// Copyright (C) 2006-2014 David Sugar, Tycho Softworks.
// Copyright (C) 2015-2020 Cherokees of Idaho.
//
// This file is part of GNU uCommon C++.
//
// GNU uCommon C++ is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GNU uCommon C++ is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with GNU uCommon C++.  If not, see <http://www.gnu.org/licenses/>.

#ifndef _MSC_VER
#include <sys/stat.h>
#endif

#ifndef _XOPEN_SOURCE
#define _XOPEN_SOURCE 600
#endif

#include <ucommon-config.h>

// broken BSD; XOPEN should not imply _POSIX_C_SOURCE,
//  _POSIX_C_SOURCE should not stop __BSD_VISIBLE

#define u_int unsigned int
#define u_short unsigned short
#define u_long unsigned long
#define u_char unsigned char

#include <ucommon/export.h>
#include <ucommon/thread.h>
#include <ucommon/fsys.h>
#include <ucommon/string.h>
#include <ucommon/typeref.h>
#include <ucommon/memory.h>
#include <ucommon/shell.h>

#ifdef  HAVE_SYSLOG_H
#include <syslog.h>
#endif

#ifdef HAVE_LINUX_VERSION_H
#include <linux/version.h>
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,8)
#ifdef  HAVE_POSIX_FADVISE
#undef  HAVE_POSIX_FADVISE
#endif
#endif
#endif

#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <ctype.h>

#ifdef  HAVE_POSIX_FADVISE
#ifndef POSIX_FADV_RANDOM
#undef  HAVE_POSIX_FADVISE
#endif
#endif

#ifdef  HAVE_DIRENT_H
#include <dirent.h>
#endif

#ifdef _MSWINDOWS_
#include <direct.h>
#include <winioctl.h>
#include <io.h>
#endif

#ifdef  HAVE_SYS_INOTIFY_H
#include <sys/inotify.h>
#endif

#ifdef  HAVE_SYS_EVENT_H
#include <sys/event.h>
#endif

namespace ucommon {

const fsys::offset_t fsys::end = (offset_t)(-1);

#ifdef  _MSWINDOWS_

// removed from some sdk versions...
struct LOCAL_REPARSE_DATA_BUFFER
{
    DWORD  ReparseTag;
    WORD   ReparseDataLength;
    WORD   Reserved;

    // IO_REPARSE_TAG_MOUNT_POINT specifics follow
    WORD   SubstituteNameOffset;
    WORD   SubstituteNameLength;
    WORD   PrintNameOffset;
    WORD   PrintNameLength;
    WCHAR  PathBuffer[1];
};

int fsys::remapError(void)
{
    DWORD err = GetLastError();

    switch(err)
    {
    case ERROR_FILE_NOT_FOUND:
    case ERROR_PATH_NOT_FOUND:
    case ERROR_INVALID_NAME:
    case ERROR_BAD_PATHNAME:
        return ENOENT;
    case ERROR_TOO_MANY_OPEN_FILES:
        return EMFILE;
    case ERROR_ACCESS_DENIED:
    case ERROR_WRITE_PROTECT:
    case ERROR_SHARING_VIOLATION:
    case ERROR_LOCK_VIOLATION:
        return EACCES;
    case ERROR_INVALID_HANDLE:
        return EBADF;
    case ERROR_NOT_ENOUGH_MEMORY:
    case ERROR_OUTOFMEMORY:
        return ENOMEM;
    case ERROR_INVALID_DRIVE:
    case ERROR_BAD_UNIT:
    case ERROR_BAD_DEVICE:
        return ENODEV;
    case ERROR_NOT_SAME_DEVICE:
        return EXDEV;
    case ERROR_NOT_SUPPORTED:
    case ERROR_CALL_NOT_IMPLEMENTED:
        return ENOSYS;
    case ERROR_END_OF_MEDIA:
    case ERROR_EOM_OVERFLOW:
    case ERROR_HANDLE_DISK_FULL:
    case ERROR_DISK_FULL:
        return ENOSPC;
    case ERROR_BAD_NETPATH:
    case ERROR_BAD_NET_NAME:
        return EACCES;
    case ERROR_FILE_EXISTS:
    case ERROR_ALREADY_EXISTS:
        return EEXIST;
    case ERROR_CANNOT_MAKE:
    case ERROR_NOT_OWNER:
        return EPERM;
    case ERROR_NO_PROC_SLOTS:
        return EAGAIN;
    case ERROR_BROKEN_PIPE:
    case ERROR_NO_DATA:
        return EPIPE;
    case ERROR_OPEN_FAILED:
        return EIO;
    case ERROR_NOACCESS:
        return EFAULT;
    case ERROR_IO_DEVICE:
    case ERROR_CRC:
    case ERROR_NO_SIGNAL_SENT:
        return EIO;
    case ERROR_CHILD_NOT_COMPLETE:
    case ERROR_SIGNAL_PENDING:
    case ERROR_BUSY:
        return EBUSY;
    case ERROR_DIR_NOT_EMPTY:
        return ENOTEMPTY;
    case ERROR_DIRECTORY:
        return ENOTDIR;
    default:
        return EINVAL;
    }
}

int dir::create(const char *path, unsigned perms)
{
    if(!CreateDirectory(path, NULL))
        return remapError();

    if(perms & 06)
        perms |= 01;
    if(perms & 060)
        perms |= 010;
    if(perms & 0600)
        perms |= 0100;

    return mode(path, perms);
}

fd_t fsys::null(void)
{
    SECURITY_ATTRIBUTES sattr;

    sattr.nLength = sizeof(SECURITY_ATTRIBUTES);
    sattr.bInheritHandle = TRUE;
    sattr.lpSecurityDescriptor = NULL;

    return CreateFile("nul", GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, &sattr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
}

int fsys::pipe(fd_t& input, fd_t& output, size_t size)
{
    input = output = NULL;
    SECURITY_ATTRIBUTES sattr;

    sattr.nLength = sizeof(SECURITY_ATTRIBUTES);
    sattr.bInheritHandle = TRUE;
    sattr.lpSecurityDescriptor = NULL;

    if(!CreatePipe(&input, &output, &sattr, (DWORD)size))
        return remapError();

    return 0;
}

int fsys::info(const char *path, struct stat *buf)
{
    if(_stat(path, (struct _stat *)(buf)))
        return remapError();
    return 0;
}

int fsys::trunc(offset_t offset)
{
    if(fsys::seek(offset) != 0)
        return remapError();

    if(SetEndOfFile(fd))
        return 0;

    return remapError();
}

int fsys::info(struct stat *buf)
{
    int fn = _open_osfhandle((intptr_t)(fd), O_RDONLY);

    int rtn = _fstat(fn, (struct _stat *)(buf));
    _close(fn);
    if(rtn)
        error = remapError();
    return rtn;
}

int fsys::prefix(const char *path)
{
    if (_chdir(path))
        return remapError();
    return 0;
}

int fsys::prefix(char *path, size_t len)
{
    if (NULL == _getcwd(path, (socksize_t)len))
        return remapError();
    return 0;
}

int fsys::mode(const char *path, unsigned value)
{
    if(_chmod(path, value))
        return remapError();
    return 0;
}

bool fsys::is_exists(const char *path)
{
    if(_access(path, F_OK))
        return false;
    return true;
}

bool fsys::is_readable(const char *path)
{
    if(_access(path, R_OK))
        return false;
    return true;
}

bool fsys::is_writable(const char *path)
{
    if(_access(path, W_OK))
        return false;
    return true;
}

bool fsys::is_executable(const char *path)
{
    path = strrchr(path, '.');

    if(!path)
        return false;

    if(eq_case(path, ".exe"))
        return true;

    if(eq_case(path, ".bat"))
        return true;

    if(eq_case(path, ".com"))
        return true;

    if(eq_case(path, ".cmd"))
        return true;

    if(eq_case(path, ".ps1"))
        return true;

    return false;
}

bool fsys::is_tty(fd_t fd)
{
    if(fd == INVALID_HANDLE_VALUE)
        return false;
    DWORD type = GetFileType(fd);
    if(type == FILE_TYPE_CHAR)
        return true;
    return false;
}

bool fsys::is_tty(void) const
{
    error = 0;
    if(fd == INVALID_HANDLE_VALUE)
        return false;
    DWORD type = GetFileType(fd);
    if(!type)
        error = remapError();
    if(type == FILE_TYPE_CHAR)
        return true;
    return false;
}

void dir::close(void)
{
    error = 0;

    if(ptr) {
        if(::FindClose(fd)) {
            delete ptr;
            ptr = NULL;
            fd = INVALID_HANDLE_VALUE;
        }
        else
            error = remapError();
    }
    else
        error = EBADF;
}

int fsys::close(void)
{
    error = 0;

    if(fd == INVALID_HANDLE_VALUE)
        return EBADF;

    if(::CloseHandle(fd))
        fd = INVALID_HANDLE_VALUE;
    else
        error = remapError();
    return error;
}

ssize_t dir::read(char *buf, size_t len)
{
    ssize_t rtn = -1;
    if(ptr) {
        snprintf((char *)buf, len, ptr->cFileName);
        rtn = (ssize_t)strlen(ptr->cFileName);
        if(!FindNextFile(fd, ptr))
            close();
        return rtn;
    }
    return -1;
}

ssize_t fsys::read(void *buf, size_t len)
{
    ssize_t rtn = -1;
    DWORD count;

    if(ReadFile(fd, (LPVOID) buf, (DWORD)len, &count, NULL))
        rtn = count;
    else
        error = remapError();

    return rtn;
}

ssize_t fsys::write(const void *buf, size_t len)
{
    ssize_t rtn = -1;
    DWORD count;

    if(WriteFile(fd, (LPVOID) buf, (DWORD)len, &count, NULL))
        rtn = count;
    else
        error = remapError();

    return rtn;
}

int fsys::sync(void)
{
    return 0;
}

fd_t fsys::input(const char *path)
{
    SECURITY_ATTRIBUTES sattr;

    sattr.nLength = sizeof(SECURITY_ATTRIBUTES);
    sattr.bInheritHandle = TRUE;
    sattr.lpSecurityDescriptor = NULL;

    return CreateFile(path, GENERIC_READ, FILE_SHARE_READ, &sattr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
}

fd_t fsys::output(const char *path)
{
    SECURITY_ATTRIBUTES sattr;

    sattr.nLength = sizeof(SECURITY_ATTRIBUTES);
    sattr.bInheritHandle = TRUE;
    sattr.lpSecurityDescriptor = NULL;

    return CreateFile(path, GENERIC_WRITE, 0, &sattr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
}

fd_t fsys::append(const char *path)
{
    SECURITY_ATTRIBUTES sattr;

    sattr.nLength = sizeof(SECURITY_ATTRIBUTES);
    sattr.bInheritHandle = TRUE;
    sattr.lpSecurityDescriptor = NULL;

    fd_t fd = CreateFile(path, GENERIC_WRITE, 0, &sattr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

    if(fd != INVALID_HANDLE_VALUE)
        SetFilePointer(fd, 0, NULL, FILE_END);

    return fd;
}

void fsys::release(fd_t fd)
{
    CloseHandle(fd);
}

void dir::open(const char *path)
{
    close();
    error = 0;

    char tpath[256];
    DWORD attr = GetFileAttributes(path);

    if((attr == (DWORD)~0l) || !(attr & FILE_ATTRIBUTE_DIRECTORY)) {
        error = ENOTDIR;
        return;
    }

    snprintf(tpath, sizeof(tpath), "%s%s", path, "\\*");
    ptr = new WIN32_FIND_DATA;
    fd = FindFirstFile(tpath, ptr);
    if(fd == INVALID_HANDLE_VALUE) {
        delete ptr;
        ptr = NULL;
        error = remapError();
    }
    return;
}

void fsys::open(const char *path, access_t access)
{
    bool append = false;
    DWORD amode = 0;
    DWORD smode = 0;
    DWORD attr = FILE_ATTRIBUTE_NORMAL;
    char buf[128];

    close();
    error = 0;

    if(access == DEVICE) {
#ifdef  _MSWINDOWS_
        if(isalpha(path[0]) && path[1] == ':') {
            if(!QueryDosDevice(path, buf, sizeof(buf))) {
                error = ENODEV;
                return;
            }
            path = buf;
        }
#else
        if(!strchr(path, '/')) {
            if(path[0] == 'S' && isdigit(path[1]))
                snprintf(buf, sizeof(buf) "/dev/tty%s", path);
            else if(strncmp(path, "USB", 3))
                snprintf(buf, sizeof(buf), "/dev/tty%s", path);
            else
                snprintf(buf, sizeof(buf), "/dev/%s", path);

            char *cp = strchr(buf, ':');
            if(cp)
                *cp = 0;
            path = buf;
        }
#endif
        if(!is_device(buf)) {
            error = ENODEV;
            return;
        }
    }

    switch(access)
    {
    case STREAM:
#ifdef  FILE_FLAG_SEQUENTIAL_SCAN
        attr |= FILE_FLAG_SEQUENTIAL_SCAN;
#endif
    case RDONLY:
        amode = GENERIC_READ;
        smode = FILE_SHARE_READ;
        break;
    case WRONLY:
        amode = GENERIC_WRITE;
        break;
    case DEVICE:
        smode = FILE_SHARE_READ;
        attr |= FILE_FLAG_WRITE_THROUGH | FILE_FLAG_NO_BUFFERING;
    case EXCLUSIVE:
        amode = GENERIC_READ | GENERIC_WRITE;
        break;
    case RANDOM:
        attr |= FILE_FLAG_RANDOM_ACCESS;
    case REWRITE:
        amode = GENERIC_READ | GENERIC_WRITE;
        smode = FILE_SHARE_READ;
        break;
    case APPEND:
        amode = GENERIC_WRITE;
        append = true;
        break;
    case SHARED:
        amode = GENERIC_READ | GENERIC_WRITE;
        smode = FILE_SHARE_READ | FILE_SHARE_WRITE;
        break;
    }

    fd = CreateFile(path, amode, smode, NULL, OPEN_EXISTING, attr, NULL);
    if(fd != INVALID_HANDLE_VALUE && append)
        seek(end);
    else if(fd == INVALID_HANDLE_VALUE)
        error = remapError();
}

void fsys::open(const char *path, unsigned fmode, access_t access)
{
    bool append = false;
    DWORD amode = 0;
    DWORD cmode = 0;
    DWORD smode = 0;
    DWORD attr = FILE_ATTRIBUTE_NORMAL;

    fmode &= 0666;

    close();
    error = 0;

    const char *cp = strrchr(path, '\\');
    const char *cp2 = strrchr(path, '/');
    if(cp2 > cp)
        cp = cp2;

    if(!cp)
        cp = path;
    else
        ++cp;

    if(*cp == '.')
        attr = FILE_ATTRIBUTE_HIDDEN;

    switch(access)
    {
    case DEVICE:
        error = ENOSYS;
        return;

    case RDONLY:
        amode = GENERIC_READ;
        cmode = OPEN_ALWAYS;
        smode = FILE_SHARE_READ;
        break;
    case STREAM:
    case WRONLY:
        amode = GENERIC_WRITE;
        cmode = CREATE_ALWAYS;
        break;
    case EXCLUSIVE:
        amode = GENERIC_READ | GENERIC_WRITE;
        cmode = OPEN_ALWAYS;
        break;
    case RANDOM:
        attr |= FILE_FLAG_RANDOM_ACCESS;
    case REWRITE:
        amode = GENERIC_READ | GENERIC_WRITE;
        cmode = OPEN_ALWAYS;
        smode = FILE_SHARE_READ;
        break;
    case APPEND:
        amode = GENERIC_WRITE;
        cmode = OPEN_ALWAYS;
        append = true;
        break;
    case SHARED:
        amode = GENERIC_READ | GENERIC_WRITE;
        cmode = OPEN_ALWAYS;
        smode = FILE_SHARE_READ | FILE_SHARE_WRITE;
        break;
    }
    fd = CreateFile(path, amode, smode, NULL, cmode, attr, NULL);
    if(fd != INVALID_HANDLE_VALUE && append)
        seek(end);
    else if(fd == INVALID_HANDLE_VALUE)
        error = remapError();
    if(fd != INVALID_HANDLE_VALUE)
        mode(path, fmode);
}

fsys::fsys(const fsys& copy)
{
    error = 0;
    fd = INVALID_HANDLE_VALUE;

    if(copy.fd == INVALID_HANDLE_VALUE)
        return;

    HANDLE pHandle = GetCurrentProcess();
    if(!DuplicateHandle(pHandle, copy.fd, pHandle, &fd, 0, FALSE, DUPLICATE_SAME_ACCESS)) {
        fd = INVALID_HANDLE_VALUE;
        error = remapError();
    }
}

int fsys::inherit(fd_t& from, bool enable)
{
    HANDLE pHandle = GetCurrentProcess();

    if(!enable) {
        if(!SetHandleInformation(from, HANDLE_FLAG_INHERIT, 0))
            return remapError();
        return 0;
    }

    fd_t fd;
    if(DuplicateHandle(pHandle, from, pHandle, &fd, 0, TRUE, DUPLICATE_SAME_ACCESS)) {
        release(from);
        from = fd;
        return 0;
    }

    return remapError();
}

fsys& fsys::operator=(fd_t from)
{
    HANDLE pHandle = GetCurrentProcess();

    if(fd != INVALID_HANDLE_VALUE) {
        if(!CloseHandle(fd)) {
            error = remapError();
            return *this;
        }
    }
    if(DuplicateHandle(pHandle, from, pHandle, &fd, 0, FALSE, DUPLICATE_SAME_ACCESS))
        error = 0;
    else {
        fd = INVALID_HANDLE_VALUE;
        error = remapError();
    }
	return *this;
}

fsys& fsys::operator=(const fsys& from)
{
    HANDLE pHandle = GetCurrentProcess();

    if(fd != INVALID_HANDLE_VALUE) {
        if(!CloseHandle(fd)) {
            error = remapError();
            return *this;
        }
    }
    if(DuplicateHandle(pHandle, from.fd, pHandle, &fd, 0, FALSE, DUPLICATE_SAME_ACCESS))
        error = 0;
    else {
        fd = INVALID_HANDLE_VALUE;
        error = remapError();
    }
	return *this;
}

int fsys::drop(offset_t size)
{
    error = ENOSYS;
    return ENOSYS;
}

int fsys::seek(offset_t pos)
{
    DWORD rpos = pos;
    int mode = FILE_BEGIN;

    if(rpos == (DWORD)end) {
        rpos = 0;
        mode = FILE_END;
    }
    if(SetFilePointer(fd, rpos, NULL, mode) == INVALID_SET_FILE_POINTER) {
        error = remapError();
        return error;
    }
    return 0;
}

#else

ssize_t dir::read(char *buf, size_t len)
{
    if(ptr) {
        dirent *entry = ::readdir((DIR *)ptr);

        if(!entry)
            return 0;

        String::set((char *)buf, len, entry->d_name);
        return strlen(entry->d_name);
    }
    return -1;
}

ssize_t fsys::read(void *buf, size_t len)
{
    int rtn = ::read(fd, buf, len);

    if(rtn < 0)
        error = remapError();
    return rtn;
}

int fsys::sync(void)
{
    int rtn = ::fsync(fd);
    if(rtn < 0)
        error = remapError();
    else
        return 0;
    return error;
}

ssize_t fsys::write(const void *buf, size_t len)
{
    int rtn = ::write(fd, buf, len);

    if(rtn < 0)
        error = remapError();
    return rtn;
}

fd_t fsys::null(void)
{
    return ::open("/dev/null", O_RDWR);
}

int fsys::pipe(fd_t& input, fd_t& output, size_t size)
{
    input = output = -1;
    int pfd[2];
    if(::pipe(pfd))
        return remapError();
    input = pfd[0];
    output = pfd[1];
    return 0;
}

bool fsys::is_tty(fd_t fd)
{
    if(isatty(fd))
        return true;
    return false;
}

bool fsys::is_tty(void) const
{
    if(isatty(fd))
        return true;
    return false;
}

void dir::close(void)
{
    error = 0;
    if(ptr) {
        if(::closedir((DIR *)ptr))
            error = remapError();
        ptr = NULL;
    }
    else
        error = EBADF;
}

int fsys::close(void)
{
    error = 0;
    if(fd != INVALID_HANDLE_VALUE) {
        if(::close(fd) == 0)
            fd = INVALID_HANDLE_VALUE;
        else
            error = remapError();
    }
    else
        return EBADF;   // not opened, but state still error free
    return error;
}

fd_t fsys::input(const char *path)
{
    return ::open(path, O_RDONLY);
}

fd_t fsys::output(const char *path)
{
    return ::open(path, O_WRONLY | O_CREAT | O_TRUNC, EVERYONE);
}

fd_t fsys::append(const char *path)
{
    return ::open(path, O_WRONLY | O_CREAT | O_APPEND, EVERYONE);
}

void fsys::release(fd_t fd)
{
    ::close(fd);
}

void fsys::open(const char *path, unsigned fmode, access_t access)
{
    unsigned flags = 0;

    close();
    error = 0;

    switch(access)
    {
    case DEVICE:
        error = ENOSYS;
        return;

    case RDONLY:
        flags = O_RDONLY | O_CREAT;
        break;
    case STREAM:
    case WRONLY:
        flags = O_WRONLY | O_CREAT | O_TRUNC;
        break;
    case RANDOM:
    case SHARED:
    case REWRITE:
    case EXCLUSIVE:
        flags = O_RDWR | O_CREAT;
        break;
    case APPEND:
        flags = O_RDWR | O_APPEND | O_CREAT;
        break;
    }
    fd = ::open(path, flags, fmode);
    if(fd == INVALID_HANDLE_VALUE)
        error = remapError();
#ifdef HAVE_POSIX_FADVISE
    else {
        if(access == RANDOM)
            posix_fadvise(fd, (off_t)0, (off_t)0, POSIX_FADV_RANDOM);
    }
#endif
}

int dir::create(const char *path, unsigned perms)
{
    if(perms & 06)
        perms |= 01;
    if(perms & 060)
        perms |= 010;
    if(perms & 0600)
        perms |= 0100;

    if(::mkdir(path, perms))
        return remapError();
    return 0;
}

void dir::open(const char *path)
{
    close();
    error = 0;

    ptr = opendir(path);
    if(!ptr)
        error = remapError();
}

void fsys::open(const char *path, access_t access)
{
    unsigned flags = 0;

    close();
    error = 0;

    switch(access)
    {
    case STREAM:
#if defined(O_STREAMING)
        flags = O_RDONLY | O_STREAMING;
        break;
#endif
    case RDONLY:
        flags = O_RDONLY;
        break;
    case WRONLY:
        flags = O_WRONLY;
        break;
    case EXCLUSIVE:
    case RANDOM:
    case SHARED:
    case REWRITE:
    case DEVICE:
        flags = O_RDWR | O_NONBLOCK;
        break;
    case APPEND:
        flags = O_RDWR | O_APPEND;
        break;
    }
    fd = ::open(path, flags);
    if(fd == INVALID_HANDLE_VALUE) {
        error = remapError();
        return;
    }
#ifdef HAVE_POSIX_FADVISE
    // Linux kernel bug prevents use of POSIX_FADV_NOREUSE in streaming...
    if(access == STREAM)
        posix_fadvise(fd, (off_t)0, (off_t)0, POSIX_FADV_SEQUENTIAL);
    else if(access == RANDOM)
        posix_fadvise(fd, (off_t)0, (off_t)0, POSIX_FADV_RANDOM);
#endif
    if(access == DEVICE) {
        flags = fcntl(fd, F_GETFL);
        flags &= ~O_NONBLOCK;
        fcntl(fd, F_SETFL, flags);
    }
}

int fsys::info(const char *path, struct stat *ino)
{
    if(::stat(path, ino))
        return remapError();
    return 0;
}

#ifdef  HAVE_FTRUNCATE
int fsys::trunc(offset_t offset)
{
    if(fsys::seek(offset) != 0)
        return remapError();

    if(::ftruncate(fd, offset) == 0)
        return 0;
    return remapError();
}
#else
int fsys::trunc(offset_t offset)
{
    if(fsys::seek(offset) != 0)
        return remapError();

    return ENOSYS;
}
#endif

int fsys::info(struct stat *ino)
{
    if(::fstat(fd, ino)) {
        error = remapError();
        return error;
    }
    return 0;
}

int fsys::prefix(const char *path)
{
    if(::chdir(path))
        return remapError();
    return 0;
}

int fsys::prefix(char *path, size_t len)
{
    if(NULL == ::getcwd(path, len))
        return remapError();
    return 0;
}

int fsys::mode(const char *path, unsigned value)
{
    if(::chmod(path, value))
        return remapError();
    return 0;
}

bool fsys::is_exists(const char *path)
{
    if(::access(path, F_OK))
        return false;

    return true;
}

bool fsys::is_readable(const char *path)
{
    if(::access(path, R_OK))
        return false;

    return true;
}

bool fsys::is_writable(const char *path)
{
    if(::access(path, W_OK))
        return false;

    return true;
}

bool fsys::is_executable(const char *path)
{
    if(is_dir(path))
        return false;

    if(::access(path, X_OK))
        return false;

    return true;
}

fsys::fsys(const fsys& copy)
{
    fd = INVALID_HANDLE_VALUE;
    error = 0;

    if(copy.fd != INVALID_HANDLE_VALUE) {
        fd = ::dup(copy.fd);
    }
    else
        fd = INVALID_HANDLE_VALUE;
}

int fsys::inherit(fd_t& fd, bool enable)
{
    unsigned long flags;
    if(fd > -1) {
        flags = fcntl(fd, F_GETFL);
        if(enable)
            flags &= ~FD_CLOEXEC;
        else
            flags |= FD_CLOEXEC;
        if(fcntl(fd, F_SETFL, flags))
            return remapError();
    }
    return 0;
}

fsys& fsys::operator=(fd_t from)
{
    close();
    if(fd == INVALID_HANDLE_VALUE && from != INVALID_HANDLE_VALUE) {
        fd = ::dup(from);
        if(fd == INVALID_HANDLE_VALUE)
            error = remapError();
    }
	return *this;
}

fsys& fsys::operator=(const fsys& from)
{
    close();
    if(fd == INVALID_HANDLE_VALUE && from.fd != INVALID_HANDLE_VALUE) {
        fd = ::dup(from.fd);
        if(fd == INVALID_HANDLE_VALUE)
            error = remapError();
    }
	return *this;
}

int fsys::drop(offset_t size)
{
#ifdef  HAVE_POSIX_FADVISE
    if(posix_fadvise(fd, (off_t)0, size, POSIX_FADV_DONTNEED)) {
        error = remapError();
        return error;
    }
    return 0;
#else
    error = ENOSYS;
    return ENOSYS;
#endif
}

int fsys::seek(offset_t pos)
{
    unsigned long rpos = pos;
    int mode = SEEK_SET;

    if(rpos == (unsigned long)end) {
        rpos = 0;
        mode = SEEK_END;
    }
    if(lseek(fd, rpos, mode) == ~0l) {
        error = remapError();
        return error;
    }
    return 0;
}

#endif

dso::dso()
{
    ptr = 0;
    error = 0;
}

dso::dso(const char *path)
{
    ptr = 0;
    error = 0;
    map(path);
}

dso::~dso()
{
    release();
}

dir::dir() :
fsys()
{
    ptr = NULL;
}

dir::~dir()
{
    close();
}

fsys::fsys()
{
    fd = INVALID_HANDLE_VALUE;
    error = 0;
}

dir::dir(const char *path) :
fsys()
{
    ptr = NULL;
    open(path);
}

fsys::fsys(const char *path, access_t access)
{
    fd = INVALID_HANDLE_VALUE;
    open(path, access);
}

fsys::fsys(const char *path, unsigned fmode, access_t access)
{
    fd = INVALID_HANDLE_VALUE;
    open(path, fmode, access);
}

fsys::~fsys()
{
    close();
}

fsys& fsys::operator*=(fd_t& from)
{
    if(fd != INVALID_HANDLE_VALUE)
        close();
    fd = from;
    from = INVALID_HANDLE_VALUE;
	return *this;
}

int fsys::linkinfo(const char *path, char *buffer, size_t size)
{
#if defined(_MSWINDOWS_)
    HANDLE h;
    DWORD rsize;
	char *reparse = (char *)_malloca(MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
	if (!reparse)
		return EINVAL;

    if(!fsys::is_link(path))
        return EINVAL;

    h = CreateFile(path, GENERIC_READ, 0, 0, OPEN_EXISTING,
        FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, 0);

    if(!h || h == INVALID_HANDLE_VALUE)
        return EINVAL;

    memset(reparse, 0, MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
    LOCAL_REPARSE_DATA_BUFFER *rb = (LOCAL_REPARSE_DATA_BUFFER*)&reparse;

    if(!DeviceIoControl(h, FSCTL_GET_REPARSE_POINT, NULL, 0, (LPVOID *)rb, MAXIMUM_REPARSE_DATA_BUFFER_SIZE, &rsize, 0)) {
        CloseHandle(h);
        return remapError();
    }

#ifdef  UNICODE
    String::set(buffer, size, rb.PathBuffer);
#else
    WideCharToMultiByte(CP_THREAD_ACP, 0, rb->PathBuffer, rb->SubstituteNameLength / sizeof(WCHAR) + 1, buffer, (int)size, "", FALSE);
#endif
    CloseHandle(h);
    return 0;
#elif defined(HAVE_READLINK)
    if(::readlink(path, buffer, size))
        return remapError();
    return 0;
#else
    return EINVAL;
#endif
}

int fsys::hardlink(const char *path, const char *target)
{
#ifdef _MSWINDOWS_
    if(!CreateHardLink(target, path, NULL))
        return remapError();
    return 0;
#else
    if(::link(path, target))
        return remapError();
    return 0;
#endif
}

int fsys::link(const char *path, const char *target)
{
#if defined(_MSWINDOWS_)
    TCHAR dest[512];
    HANDLE h;
    char *part;
    DWORD size;
    WORD len;
	char *reparse = (char *)_malloca(MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
	if (!reparse)
		return EINVAL;

    lstrcpy(dest, "\\??\\");
	dest[4] = 0;
    if(!GetFullPathName(path, sizeof(dest) - (4 * sizeof(TCHAR)), &dest[4], &part) || GetFileAttributes(&dest[4]) == INVALID_FILE_ATTRIBUTES)
        return remapError();

    LOCAL_REPARSE_DATA_BUFFER *rb = (LOCAL_REPARSE_DATA_BUFFER*)&reparse;
	memset(rb, 0, MAXIMUM_REPARSE_DATA_BUFFER_SIZE);

    if(!MultiByteToWideChar(CP_THREAD_ACP, MB_PRECOMPOSED, dest, lstrlenA(dest) + 1, rb->PathBuffer, lstrlenA(dest) + 1))
        return remapError();

    len = lstrlenW(rb->PathBuffer) * 2;
    rb->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT;
    rb->ReparseDataLength = len + 12;
    rb->SubstituteNameLength = len;
    rb->PrintNameOffset = len + 2;
    h = CreateFile(target, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING,
        FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, 0);
    if(!h || h == INVALID_HANDLE_VALUE)
        return hardlink(path, target);
    if(!DeviceIoControl(h, FSCTL_SET_REPARSE_POINT, (LPVOID)rb, rb->ReparseDataLength + FIELD_OFFSET(LOCAL_REPARSE_DATA_BUFFER, SubstituteNameOffset), NULL, 0, &size, 0)) {
        CloseHandle(h);
        return hardlink(path, target);
    }
    CloseHandle(h);
    return 0;
#elif defined(HAVE_SYMLINK)
    if(::symlink(path, target))
        return remapError();
    return 0;
#else
    if(::link(path, target))
        return remapError();
    return 0;
#endif
}

int fsys::unlink(const char *path)
{
#ifdef  _MSWINDOWS_
    HANDLE h = INVALID_HANDLE_VALUE;
    if(is_link(path))
        h = CreateFile(path, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING,
            FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, 0);
    if(h && h != INVALID_HANDLE_VALUE) {
        REPARSE_GUID_DATA_BUFFER rb;
        memset(&rb, 0, sizeof(rb));
        DWORD size;
        rb.ReparseTag = IO_REPARSE_TAG_MOUNT_POINT;
        if(!DeviceIoControl(h, FSCTL_DELETE_REPARSE_POINT, &rb,
            REPARSE_GUID_DATA_BUFFER_HEADER_SIZE, NULL, 0, &size, 0)) {
            CloseHandle(h);
            return remapError();
        }
        CloseHandle(h);
        ::remove(path);
        return 0;
    }
#endif
    if(::remove(path))
        return remapError();
    return 0;
}

int fsys::erase(const char *path)
{
    if(is_device(path))
        return ENOSYS;

    if(::remove(path))
        return remapError();
    return 0;
}

int dir::remove(const char *path)
{
    if(is_device(path))
        return ENOSYS;

#ifdef  _MSWINDOWS_
    if(RemoveDirectory(path))
        return 0;
    int error = remapError();
    if(error == ENOTEMPTY)
        return ENOTEMPTY;
#else
    if(!::rmdir(path))
        return 0;
    if(errno != ENOTDIR)
        return errno;
#endif

    if(::remove(path))
        return remapError();
    return 0;
}

int fsys::copy(const char *oldpath, const char *newpath, size_t size)
{
    int result = 0;
    char *buffer = new char[size];
    fsys src, dest;
    ssize_t count = (ssize_t)size;

    if(!buffer) {
        result = ENOMEM;
        goto end;
    }

    remove(newpath);

    src.open(oldpath, fsys::STREAM);
    if(!is(src))
        goto end;

    dest.open(newpath, GROUP_PUBLIC, fsys::STREAM);
    if(!is(dest))
        goto end;

    while(count > 0) {
        count = src.read(buffer, size);
        if(count < 0) {
            result = src.err();
            goto end;
        }
        if(count > 0)
            count = dest.write(buffer, size);
        if(count < 0) {
            result = dest.err();
            goto end;
        }
    }

end:
    if(is(src))
        src.close();

    if(is(dest))
        dest.close();

    if(buffer)
        delete[] buffer;

    if(result != 0)
        remove(newpath);

    return result;
}

int fsys::rename(const char *oldpath, const char *newpath)
{
    if(::rename(oldpath, newpath))
        return remapError();
    return 0;
}

int fsys::load(const char *path)
{
    dso module;

    module.map(path);
#ifdef  _MSWINDOWS_
    if(module.ptr) {
        module.ptr = 0;
        return 0;
    }
    return remapError();
#else
    if(module.ptr) {
        module.ptr = 0;
        return 0;
    }
    return module.error;
#endif
}

bool fsys::is_file(const char *path)
{
#ifdef _MSWINDOWS_
    DWORD attr = GetFileAttributes(path);
    if(attr == (DWORD)~0l)
        return false;

    if(attr & FILE_ATTRIBUTE_DIRECTORY)
        return false;

    return true;

#else
    struct stat ino;

    if(::stat(path, &ino))
        return false;

    if(S_ISREG(ino.st_mode))
        return true;

    return false;
#endif
}

bool fsys::is_link(const char *path)
{
#if defined(_MSWINDOWS_)
    DWORD attr = GetFileAttributes(path);
    if (attr == 0xffffffff || !(attr & FILE_ATTRIBUTE_REPARSE_POINT))
        return false;
    return true;
#elif defined(HAVE_LSTAT)
    struct stat ino;

    if(::lstat(path, &ino))
        return false;

    if(S_ISLNK(ino.st_mode))
        return true;

    return false;
#else
    return false;
#endif
}

bool fsys::is_dir(const char *path)
{
#ifdef _MSWINDOWS_
    DWORD attr = GetFileAttributes(path);
    if(attr == (DWORD)~0l)
        return false;

    if(attr & FILE_ATTRIBUTE_DIRECTORY)
        return true;

    return false;

#else
    struct stat ino;

    if(::stat(path, &ino))
        return false;

    if(S_ISDIR(ino.st_mode))
        return true;

    return false;
#endif
}

#ifdef  _MSWINDOWS_

void dso::map(const char *path)
{
    error = 0;
    ptr = LoadLibrary(path);
    if(!ptr)
        error = ENOEXEC;
}

void dso::release(void)
{
    if(ptr)
        FreeLibrary(ptr);
    ptr = 0;
}

dso::addr_t dso::find(const char *sym) const
{
    if(ptr == 0)
        return (dso::addr_t)NULL;

    return (addr_t)GetProcAddress(ptr, sym);
}

#elif defined(HAVE_DLFCN_H)
#include <dlfcn.h>

#ifndef RTLD_GLOBAL
#define RTLD_GLOBAL 0
#endif

void dso::map(const char *path)
{
    error = 0;
    ptr = dlopen(path, RTLD_NOW | RTLD_GLOBAL);
    if(ptr == NULL)
        error = ENOEXEC;
}

void dso::release(void)
{
    if(ptr)
        dlclose(ptr);
    ptr = NULL;
}

dso::addr_t dso::find(const char *sym) const
{
    if(!ptr)
        return (dso::addr_t)NULL;

    return (dso::addr_t)dlsym(ptr, (char *)sym);
}

#elif HAVE_MACH_O_DYLD_H
#include <mach-o/dyld.h>

void dso::map(const char *path)
{
    NSObjectFileImage oImage;
    NSSymbol sym = NULL;
    NSModule mod;
    void (*init)(void);

    ptr = NULL;
    error = 0;

    if(NSCreateObjectFileImageFromFile(path, &oImage) != NSObjectFileImageSuccess) {
        error = ENOEXEC;
        return;
    }

    mod = NSLinkModule(oImage, path, NSLINKMODULE_OPTION_BINDNOW | NSLINKMODULE_OPTION_RETURN_ON_ERROR);
    NSDestroyObjectFileImage(oImage);
    if(mod == NULL) {
        error = ENOEXEC;
        return;
    }

    sym = NSLookupSymbolInModule(mod, "__init");
    if(sym) {
        init = (void (*)(void))NSAddressOfSymbol(sym);
        init();
    }
    ptr = (void *)mod;
}

void dso::release(void)
{
    if(!ptr)
        return;

    NSModule mod = (NSModule)ptr;
    NSSymbol sym;
    void (*fini)(void);
    ptr = NULL;

    sym = NSLookupSymbolInModule(mod, "__fini");
    if(sym != NULL) {
        fini = (void (*)(void))NSAddressOfSymbol(sym);
        fini();
    }
    NSUnlinkModule(mod, NSUNLINKMODULE_OPTION_NONE);
}

dso::addr_t dso::find(const char *sym) const
{
    if(!ptr)
        return NULL;

    NSModule mod = (NSModule)ptr;
    NSSymbol sym;

    sym = NSLookupSymbolInModule(mod, sym);
    if(sym != NULL) {
        return (dso::addr_t)NSAddressOfSymbol(sym);

    return (dso::addr_t)NULL;
}

#elif HAVE_SHL_LOAD
#include <dl.h>

void dso::map(const char *path)
{
    error = 0;
    ptr = (void *)shl_load(path, BIND_IMMEDIATE, 0l);
    if(!ptr)
        error = ENOEXEC;
}

dso::addr_t dso::find(const char *sym) const
{
    if(!ptr)
        return (dso::addr_t)NULL;

    shl_t image = (shl_t)ptr;

    if(shl_findsym(&image, sym, 0, &value) == 0)
        return (dso::addr_t)value;

    return (dso::addr_t)NULL;
}

void dso::release(void)
{
    shl_t image = (shl_t)ptr;
    if(ptr)
        shl_unload(image);
    ptr = NULL;
}

#else

void fsys::map(const char *path)
{
    error = ENOEXEC;
    ptr = NULL;
}

void dso::release(void)
{
}

dso::addr_t dso::find(const char *sym) const
{
    return (dso::addr_t)NULL;
}

#endif

bool fsys::is_device(const char *path)
{
    if(!path)
        return false;

#ifndef _MSWINDOWS_
    if(is_dir(path))
        return false;

    if(!strncmp(path, "/dev/", 5))
        return true;

    return false;
#else
    if(path[1] == ':' && !path[2] && isalpha(*path))
        return true;

    if(!strncmp(path, "com", 3) || !strncmp(path, "lpt", 3)) {
        path += 3;
        while(isdigit(*path))
            ++path;
        if(!path || *path == ':')
            return true;
        return false;
    }

    if(!strcmp(path, "aux") || !strcmp(path, "prn")) {
        if(!path[3] || path[3] == ':')
            return true;
        return false;
    }

    if(!strncmp(path, "\\\\.\\", 4))
        return true;

    if(!strnicmp(path, "\\\\?\\Device\\", 12))
        return true;

    return false;

#endif
}

bool fsys::is_hidden(const char *path)
{
#ifdef  _MSWINDOWS_
    DWORD attr = GetFileAttributes(path);
    if(attr == (DWORD)~0l)
        return false;

    return ((attr & FILE_ATTRIBUTE_HIDDEN) != 0);
#else
    const char *cp = strrchr(path, '/');
    if(cp)
        ++cp;
    else
        cp = path;

    if(*cp == '.')
        return true;

    return false;
#endif
}

fsys::fsys(fd_t handle)
{
    fd = handle;
    error = 0;
}

void fsys::set(fd_t handle)
{
    close();
    fd = handle;
    error = 0;
}

fd_t fsys::release(void)
{
    fd_t save = fd;

    fd = INVALID_HANDLE_VALUE;
    error = 0;
    return save;
}

int fsys::exec(const char *path, char **argv, char **envp)
{
    shell::pid_t pid = shell::spawn(path, argv, envp);
    return shell::wait(pid);
}

#ifdef _MSWINDOWS_

stringref_t fsys::prefix(void)
{
    char *cp = _getcwd(NULL, 0);
    stringref_t result(cp);
    if(cp)
        ::free(cp);
    return result;
}

#else

stringref_t fsys::prefix(void)
{
    size_t size = 40;
    charvalues_t buf = stringref<auto_release>::create(40);
    stringref_t out;
    for(;;) {
        if(NULL != (getcwd(buf->get(), buf->max())))
            break;
        if(errno != ERANGE) {
            *(buf->get()) = 0;
            break;
        }
        stringref<auto_release>::expand(&buf, size);
        size += 40;
    }
    out.assign(buf);
    return out;
}

#endif

} // namespace ucommon
