/*
 * %CopyrightBegin%
 *
 * Copyright Ericsson AB 1997-2012. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * %CopyrightEnd%
 */
/*
 * Purpose: Provides file and directory operations for OSE.
 */
#ifdef HAVE_CONFIG_H
#  include "config.h"
#endif
#if defined(HAVE_POSIX_FALLOCATE) && !defined(__sun) && !defined(__sun__)
#define _XOPEN_SOURCE 600
#endif
#if !defined(_GNU_SOURCE) && defined(HAVE_LINUX_FALLOC_H)
#define _GNU_SOURCE
#endif
#include "sys.h"
#include "erl_driver.h"
#include "erl_efile.h"
#if defined(DARWIN) || defined(HAVE_LINUX_FALLOC_H) || defined(HAVE_POSIX_FALLOCATE)
#include "fcntl.h"
#endif
#include "ose.h"
#include "unistd.h"
#include "sys/stat.h"
#include "dirent.h"
#include "sys/time.h"
#include "time.h"
#include "assert.h"

/* Find a definition of MAXIOV, that is used in the code later. */
#if defined IOV_MAX
#define MAXIOV IOV_MAX
#elif defined UIO_MAXIOV
#define MAXIOV UIO_MAXIOV
#else
#define MAXIOV 16
#endif

/*
 * Macros for testing file types.
 */

#define ISDIR(st) (((st).st_mode & S_IFMT) == S_IFDIR)
#define ISREG(st) (((st).st_mode & S_IFMT) == S_IFREG)
#define ISDEV(st) \
    (((st).st_mode&S_IFMT) == S_IFCHR || ((st).st_mode&S_IFMT) == S_IFBLK)
#define ISLNK(st) (((st).st_mode & S_IFLNK) == S_IFLNK)
#ifdef NO_UMASK
#define FILE_MODE 0644
#define DIR_MODE  0755
#else
#define FILE_MODE 0666
#define DIR_MODE  0777
#endif

#define IS_DOT_OR_DOTDOT(s) \
    (s[0] == '.' && (s[1] == '\0' || (s[1] == '.' && s[2] == '\0')))

/*
 * Macros for handling local file descriptors
 * and mutexes.
 *
 * Handling of files like this is necessary because OSE
 * does not allow seeking after the end of a file. So
 * what we do it emulate this by keeping track of the size
 * of the file and where the file's positions is. If a
 * write happens after eof then we pad it.
 *
 * Given time this should be rewritten to get rid of the
 * mutex and use the port lock to protect the data. This
 * could be done be done by adapting the efile api for some
 * calls to allow some meta-data to be associated with the
 * open file.
 */

#define L_FD_IS_VALID(fd_data) ((fd_data)->beyond_eof > 0)
#define L_FD_INVALIDATE(fd_data) (fd_data)->beyond_eof = 0
#define L_FD_CUR(fd_data) (fd_data)->pos
#define L_FD_OFFS_BEYOND_EOF(fd_data, offs) \
    (((fd_data)->size > offs) ? 0 : 1)

#define L_FD_FAIL -1
#define L_FD_SUCCESS 1
#define L_FD_PAD_SIZE 255

struct fd_meta {
    ErlDrvMutex *meta_mtx;
    struct fd_data *fd_data_list;
};

struct fd_data {
    int fd;
    struct fd_data *next;
    struct fd_data *prev;
    int pos;
    int beyond_eof;
    size_t size;
#ifdef DEBUG
    PROCESS owner;
#endif
};

static int l_invalidate_local_fd(int fd);
static int l_pad_file(struct fd_data *fd_data, off_t offset);
static int check_error(int result, Efile_error* errInfo);
static struct fd_data* l_new_fd(void);
static int l_remove_local_fd(int fd);
static struct fd_data* l_find_local_fd(int fd);
static int l_update_local_fd(int fd, int pos, int size);

static struct fd_meta* fdm = NULL;


/***************************************************************************/

static int
l_remove_local_fd(int fd)
{
    struct fd_data *fd_data;
    fd_data = l_find_local_fd(fd);

    if (fd_data == NULL) {
        return L_FD_FAIL;
    }
#ifdef DEBUG
    assert(fd_data->owner == current_process());
#endif
    erl_drv_mutex_lock(fdm->meta_mtx);
    /* head ? */
    if (fd_data == fdm->fd_data_list) {
        if (fd_data->next != NULL) {
            /* remove link to head */
            fd_data->next->prev = NULL;
            /* set new head */
            fdm->fd_data_list = fd_data->next;
        }
        else {
            /* head is lonely */
            fdm->fd_data_list = NULL;
        }
    }
    else { /* not head */
        if (fd_data->prev == NULL) {
            erl_drv_mutex_unlock(fdm->meta_mtx);
            return L_FD_FAIL;
        }
        else {
            if (fd_data->next != NULL) {
                fd_data->next->prev = fd_data->prev;
                fd_data->prev->next = fd_data->next;
            }
            else {
                fd_data->prev->next = NULL;
            }
        }
    }

    /* scramble values */
    fd_data->beyond_eof = -1;
    fd_data->next = NULL;
    fd_data->prev = NULL;
    fd_data->fd = -1;

    /* unlock and clean */
    driver_free(fd_data);
    erl_drv_mutex_unlock(fdm->meta_mtx);

    return L_FD_SUCCESS;
}

/***************************************************************************/

static int
l_invalidate_local_fd(int fd) {
    struct fd_data *fd_data;

    if ((fd_data = l_find_local_fd(fd)) == NULL) {
        return L_FD_FAIL;
    }

    fd_data->beyond_eof = 0;
    return L_FD_SUCCESS;
}

/****************************************************************************/

static struct fd_data*
l_find_local_fd(int fd) {
    struct fd_data *fd_data;

    fd_data = NULL;
    erl_drv_mutex_lock(fdm->meta_mtx);
    for (fd_data = fdm->fd_data_list; fd_data != NULL; ) {
        if (fd_data->fd == fd) {
#ifdef DEBUG
            assert(fd_data->owner == current_process());
#endif
            break;
        }
        fd_data = fd_data->next;
    }
    erl_drv_mutex_unlock(fdm->meta_mtx);
    return fd_data;
}

/***************************************************************************/

static struct fd_data*
l_new_fd(void) {
    struct fd_data *fd_data;

    fd_data = driver_alloc(sizeof(struct fd_data));
    if (fd_data == NULL) {
        return NULL;
    }
    erl_drv_mutex_lock(fdm->meta_mtx);
    if (fdm->fd_data_list == NULL) {
        fdm->fd_data_list = fd_data;
        fdm->fd_data_list->prev = NULL;
        fdm->fd_data_list->next = NULL;
    }
    else {
        fd_data->next = fdm->fd_data_list;
        fdm->fd_data_list = fd_data;
        fdm->fd_data_list->prev = NULL;
    }
#ifdef DEBUG
    fd_data->owner = current_process();
#endif
    erl_drv_mutex_unlock(fdm->meta_mtx);
    return fd_data;
}

/***************************************************************************/

static int
l_update_local_fd(int fd, int pos, int size) {
    struct fd_data *fd_data = NULL;

    fd_data = l_find_local_fd(fd);
    /* new fd to handle? */
    if (fd_data == NULL) {
        fd_data = l_new_fd();
        if (fd_data == NULL) {
            /* out of memory */
            return L_FD_FAIL;
        }
    }
    fd_data->size = size;
    fd_data->pos = pos;
    fd_data->fd = fd;
    fd_data->beyond_eof = 1;

    return L_FD_SUCCESS;
}

/***************************************************************************/

static int
l_pad_file(struct fd_data *fd_data, off_t offset) {
    int size_dif;
    int written = 0;
    int ret_val = L_FD_SUCCESS;
    char padding[L_FD_PAD_SIZE];

    size_dif = (offset - fd_data->size);
    memset(&padding, '\0', L_FD_PAD_SIZE);

    while (size_dif > 0) {
        written = write(fd_data->fd, padding,
                (size_dif < L_FD_PAD_SIZE) ?
                size_dif : L_FD_PAD_SIZE);
        if (written < 0 && errno != EINTR && errno != EAGAIN) {
            ret_val = -1;
            break;
        }
        size_dif -= written;
    }
    L_FD_INVALIDATE(fd_data);
    return ret_val;
}

/***************************************************************************/

static int
check_error(int result, Efile_error *errInfo) {
    if (result < 0) {
        errInfo->posix_errno = errInfo->os_errno = errno;
        return 0;
    }
    return 1;
}

/***************************************************************************/

int
efile_init() {
    fdm = driver_alloc(sizeof(struct fd_meta));
    if (fdm == NULL) {
        return L_FD_FAIL;
    }
    fdm->meta_mtx = erl_drv_mutex_create("ose_efile local fd mutex\n");
    erl_drv_mutex_lock(fdm->meta_mtx);
    fdm->fd_data_list = NULL;
    erl_drv_mutex_unlock(fdm->meta_mtx);
    return L_FD_SUCCESS;
}

/***************************************************************************/

int
efile_mkdir(Efile_error* errInfo,       /* Where to return error codes. */
        char* name)                 /* Name of directory to create. */
{
#ifdef NO_MKDIR_MODE
    return check_error(mkdir(name), errInfo);
#else
    int res = mkdir(name, DIR_MODE);
    if (res < 0 && errno == EINVAL) {
      errno = ENOENT;
    }
    return check_error(res, errInfo);
#endif
}

/***************************************************************************/

int
efile_rmdir(Efile_error* errInfo,       /* Where to return error codes. */
        char* name)                 /* Name of directory to delete. */
{
    if (rmdir(name) == 0) {
        return 1;
    }
    if (errno == ENOTEMPTY) {
        errno = EEXIST;
    }
    if (errno == EEXIST || errno == EINVAL) {
        int saved_errno = errno;
        struct stat file_stat;
        struct stat cwd_stat;

        if(stat(name, &file_stat) != 0) {
            errno = ENOENT;
            return check_error(-1, errInfo);
        }
        /*
         *  The error code might be wrong if this is the current directory.
         */
        if (stat(name, &file_stat) == 0 && stat(".", &cwd_stat) == 0 &&
                file_stat.st_ino == cwd_stat.st_ino &&
                file_stat.st_dev == cwd_stat.st_dev) {
            saved_errno = EACCES;
        }
        errno = saved_errno;
    }
    return check_error(-1, errInfo);
}

/***************************************************************************/

int
efile_delete_file(Efile_error* errInfo, /* Where to return error codes. */
        char* name)           /* Name of file to delete. */
{
    struct stat statbuf;

    if (stat(name, &statbuf) >= 0) {
        /* Do not let unlink() remove directories */
        if (ISDIR(statbuf)) {
            errno = EPERM;
            return check_error(-1, errInfo);
        }

        if (unlink(name) == 0) {
            return 1;
        }

        if (errno == EISDIR) {
            errno = EPERM;
            return check_error(-1, errInfo);
        }
    }
    else {
        if (errno == EINVAL) {
            errno = ENOENT;
            return check_error(-1, errInfo);
        }
    }
    return check_error(-1, errInfo);
}

/*
 *---------------------------------------------------------------------------
 *
 *      Changes the name of an existing file or directory, from src to dst.
 *      If src and dst refer to the same file or directory, does nothing
 *      and returns success.  Otherwise if dst already exists, it will be
 *      deleted and replaced by src subject to the following conditions:
 *          If src is a directory, dst may be an empty directory.
 *          If src is a file, dst may be a file.
 *      In any other situation where dst already exists, the rename will
 *      fail.
 *
 * Results:
 *      If the directory was successfully created, returns 1.
 *      Otherwise the return value is 0 and errno is set to
 *      indicate the error.  Some possible values for errno are:
 *
 *      EACCES:     src or dst parent directory can't be read and/or written.
 *      EEXIST:     dst is a non-empty directory.
 *      EINVAL:     src is a root directory or dst is a subdirectory of src.
 *      EISDIR:     dst is a directory, but src is not.
 *      ENOENT:     src doesn't exist, or src or dst is "".
 *      ENOTDIR:    src is a directory, but dst is not.
 *      EXDEV:      src and dst are on different filesystems.
 *
 * Side effects:
 *      The implementation of rename may allow cross-filesystem renames,
 *      but the caller should be prepared to emulate it with copy and
 *      delete if errno is EXDEV.
 *
 *---------------------------------------------------------------------------
 */

int
efile_rename(Efile_error* errInfo,      /* Where to return error codes. */
        char* src,                 /* Original name. */
        char* dst)                 /* New name. */
{

    /* temporary fix AFM does not recognize ./<file name>
     * in destination remove pending on adaption of AFM fix
     */

    char *dot_str;
    if (dst != NULL) {
        dot_str = strchr(dst, '.');
        if (dot_str && dot_str == dst && dot_str[1] == '/') {
            dst = dst+2;
        }
    }

    if (rename(src, dst) == 0) {
        return 1;
    }
    if (errno == ENOTEMPTY) {
        errno = EEXIST;
    }
    if (errno == EINVAL) {
        struct stat file_stat;

        if (stat(dst, &file_stat)== 0) {
            if (ISDIR(file_stat)) {
                errno = EISDIR;
            }
            else if (ISREG(file_stat)) {
                errno = ENOTDIR;
            }
            else {
                errno = EINVAL;
            }
        }
        else {
            errno = EINVAL;
        }
    }

    if (strcmp(src, "/") == 0) {
        errno = EINVAL;
    }
    return check_error(-1, errInfo);
}

/***************************************************************************/

int
efile_chdir(Efile_error* errInfo,   /* Where to return error codes. */
        char* name)             /* Name of directory to make current. */
{
    return check_error(chdir(name), errInfo);
}

/***************************************************************************/

int
efile_getdcwd(Efile_error* errInfo,     /* Where to return error codes. */
        int drive,                /* 0 - current, 1 - A, 2 - B etc. */
        char* buffer,             /* Where to return the current
                                     directory. */
        size_t size)              /* Size of buffer. */
{
    if (drive == 0) {
        if (getcwd(buffer, size) == NULL)
            return check_error(-1, errInfo);

        return 1;
    }

    /*
     * Drives other than 0 is not supported on Unix.
     */

    errno = ENOTSUP;
    return check_error(-1, errInfo);
}

/***************************************************************************/

int
efile_readdir(Efile_error* errInfo,     /* Where to return error codes. */
        char* name,               /* Name of directory to open. */
        EFILE_DIR_HANDLE* p_dir_handle,   /* Pointer to directory
                                             handle of
                                             open directory.*/
        char* buffer,             /* Pointer to buffer for
                                     one filename. */
        size_t *size)             /* in-out Size of buffer, length
                                     of name. */
{
    DIR *dp;                    /* Pointer to directory structure. */
    struct dirent* dirp;        /* Pointer to directory entry. */

    /*
     * If this is the first call, we must open the directory.
     */

    if (*p_dir_handle == NULL) {
        dp = opendir(name);
        if (dp == NULL)
            return check_error(-1, errInfo);
        *p_dir_handle = (EFILE_DIR_HANDLE) dp;
    }

    /*
     * Retrieve the name of the next file using the directory handle.
     */

    dp = *((DIR **)((void *)p_dir_handle));
    for (;;) {
        dirp = readdir(dp);
        if (dirp == NULL) {
            closedir(dp);
            return 0;
        }
        if (IS_DOT_OR_DOTDOT(dirp->d_name))
            continue;
        buffer[0] = '\0';
        strncat(buffer, dirp->d_name, (*size)-1);
        *size = strlen(dirp->d_name);
        return 1;
    }
}

/***************************************************************************/

int
efile_openfile(Efile_error* errInfo,    /* Where to return error codes. */
        char* name,              /* Name of directory to open. */
        int flags,               /* Flags to user for opening. */
        int* pfd,                /* Where to store the file
                                    descriptor. */
        Sint64 *pSize)           /* Where to store the size of the
                                    file. */
{
    struct stat statbuf;
    int fd;
    int mode;                   /* Open mode. */

    if (stat(name, &statbuf) >= 0 && !ISREG(statbuf)) {
        errno = EISDIR;
        return check_error(-1, errInfo);
    }

    switch (flags & (EFILE_MODE_READ|EFILE_MODE_WRITE)) {
        case EFILE_MODE_READ:
            mode = O_RDONLY;
            break;
        case EFILE_MODE_WRITE:
            if (flags & EFILE_NO_TRUNCATE)
                mode = O_WRONLY | O_CREAT;
            else
                mode = O_WRONLY | O_CREAT | O_TRUNC;
            break;
        case EFILE_MODE_READ_WRITE:
            mode = O_RDWR | O_CREAT;
            break;
        default:
            errno = EINVAL;
            return check_error(-1, errInfo);
    }


    if (flags & EFILE_MODE_APPEND) {
        mode &= ~O_TRUNC;
        mode |= O_APPEND;
    }

    if (flags & EFILE_MODE_EXCL) {
        mode |= O_EXCL;
    }

    fd = open(name, mode, FILE_MODE);

    if (!check_error(fd, errInfo))
        return 0;

    *pfd = fd;
    if (pSize) {
        *pSize = statbuf.st_size;
    }
    return 1;
}

/***************************************************************************/

int
efile_may_openfile(Efile_error* errInfo, char *name) {
    struct stat statbuf;        /* Information about the file */
    int result;

    result = stat(name, &statbuf);
    if (!check_error(result, errInfo))
        return 0;
    if (!ISREG(statbuf)) {
        errno = EISDIR;
        return check_error(-1, errInfo);
    }
    return 1;
}

/***************************************************************************/

void
efile_closefile(int fd)
{
    if (l_find_local_fd(fd) != NULL) {
        l_remove_local_fd(fd);
    }
    close(fd);
}

/***************************************************************************/

int
efile_fdatasync(Efile_error *errInfo, /* Where to return error codes. */
        int fd)               /* File descriptor for file to sync data. */
{
    return efile_fsync(errInfo, fd);
}

/***************************************************************************/

int
efile_fsync(Efile_error *errInfo, /* Where to return error codes. */
        int fd)               /* File descriptor for file to sync. */
{
    return check_error(fsync(fd), errInfo);
}

/***************************************************************************/

int
efile_fileinfo(Efile_error* errInfo, Efile_info* pInfo,
        char* name, int info_for_link)
{
    struct stat statbuf;        /* Information about the file */
    int result;

    result = stat(name, &statbuf);
    if (!check_error(result, errInfo)) {
        return 0;
    }

#if SIZEOF_OFF_T == 4
    pInfo->size_high = 0;
#else
    pInfo->size_high = (Uint32)(statbuf.st_size >> 32);
#endif
    pInfo->size_low = (Uint32)statbuf.st_size;

#ifdef NO_ACCESS
    /* Just look at read/write access for owner. */

    pInfo->access = ((statbuf.st_mode >> 6) & 07) >> 1;

#else
    pInfo->access = FA_NONE;
    if (access(name, R_OK) == 0)
        pInfo->access |= FA_READ;
    if (access(name, W_OK) == 0)
        pInfo->access |= FA_WRITE;

#endif

    if (ISDEV(statbuf))
        pInfo->type = FT_DEVICE;
    else if (ISDIR(statbuf))
        pInfo->type = FT_DIRECTORY;
    else if (ISREG(statbuf))
        pInfo->type = FT_REGULAR;
    else if (ISLNK(statbuf))
        pInfo->type = FT_SYMLINK;
    else
        pInfo->type = FT_OTHER;

    pInfo->accessTime   = statbuf.st_atime;
    pInfo->modifyTime   = statbuf.st_mtime;
    pInfo->cTime        = statbuf.st_ctime;

    pInfo->mode         = statbuf.st_mode;
    pInfo->links        = statbuf.st_nlink;
    pInfo->major_device = statbuf.st_dev;
    pInfo->inode        = statbuf.st_ino;
    pInfo->uid          = statbuf.st_uid;
    pInfo->gid          = statbuf.st_gid;

    return 1;
}

/***************************************************************************/

int
efile_write_info(Efile_error *errInfo, Efile_info *pInfo, char *name)
{
    /*
     * On some systems chown will always fail for a non-root user unless
     * POSIX_CHOWN_RESTRICTED is not set.  Others will succeed as long as
     * you don't try to chown a file to someone besides youself.
     */
    if (pInfo->mode != -1) {
        mode_t newMode = pInfo->mode & (S_ISUID | S_ISGID |
                S_IRWXU | S_IRWXG | S_IRWXO);
        if (chmod(name, newMode)) {
            newMode &= ~(S_ISUID | S_ISGID);
            if (chmod(name, newMode)) {
                return check_error(-1, errInfo);
            }
        }
    }

    return 1;
}

/***************************************************************************/

int
efile_write(Efile_error* errInfo,       /* Where to return error codes. */
        int flags,                  /* Flags given when file was
                                       opened. */
        int fd,                     /* File descriptor to write to. */
        char* buf,                  /* Buffer to write. */
        size_t count)               /* Number of bytes to write. */
{
    ssize_t written;                    /* Bytes written in last operation. */
    struct fd_data *fd_data;

    if ((fd_data = l_find_local_fd(fd)) != NULL) {
        if (L_FD_IS_VALID(fd_data)) {
            /* we are beyond eof and need to pad*/
            if (l_pad_file(fd_data, L_FD_CUR(fd_data)) < 0) {
                return check_error(-1, errInfo);
            }
        }
    }

    while (count > 0)  {
        if ((written = write(fd, buf, count)) < 0) {
            if (errno != EINTR) {
                return check_error(-1, errInfo);
            }
            else {
                written = 0;
            }
        }
        ASSERT(written <= count);
        buf += written;
        count -= written;
    }
    return 1;
}

/***************************************************************************/

int
efile_writev(Efile_error* errInfo,   /* Where to return error codes */
        int flags,              /* Flags given when file was
                                 * opened */
        int fd,                 /* File descriptor to write to */
        SysIOVec* iov,          /* Vector of buffer structs.
                                 * The structs may be changed i.e.
                                 * due to incomplete writes */
        int iovcnt)             /* Number of structs in vector */
{
    struct fd_data *fd_data;
    int cnt = 0;                     /* Buffers so far written */

    ASSERT(iovcnt >= 0);
    if ((fd_data = l_find_local_fd(fd)) != NULL) {
        if (L_FD_IS_VALID(fd_data)) {
            /* we are beyond eof and need to pad*/
            if (l_pad_file(fd_data, L_FD_CUR(fd_data)) < 0) {
                return check_error(-1, errInfo);
            }
        }
    }
    while (cnt < iovcnt) {
        if ((! iov[cnt].iov_base) || (iov[cnt].iov_len <= 0)) {
            /* Empty buffer - skip */
            cnt++;
        }
        else { /* Non-empty buffer */
            ssize_t w;                   /* Bytes written in this call */
            do {
                w = write(fd, iov[cnt].iov_base, iov[cnt].iov_len);
            } while (w < 0 && errno == EINTR);

            ASSERT(w <= iov[cnt].iov_len || w == -1);

            if (w < 0) {
                return check_error(-1, errInfo);
            }
            /* Move forward to next buffer to write */
            for (; cnt < iovcnt && w > 0; cnt++) {
                if (iov[cnt].iov_base && iov[cnt].iov_len > 0) {
                    if (w < iov[cnt].iov_len) {
                        /* Adjust the buffer for next write */
                        iov[cnt].iov_len -= w;
                        iov[cnt].iov_base += w;
                        w = 0;
                        break;
                    }
                    else {
                        w -= iov[cnt].iov_len;
                    }
                }
            }
            ASSERT(w == 0);
        } /* else Non-empty buffer */
    } /* while (cnt< iovcnt) */
    return 1;
}

/***************************************************************************/

int
efile_read(Efile_error* errInfo,     /* Where to return error codes. */
        int flags,                  /* Flags given when file was opened. */
        int fd,                     /* File descriptor to read from. */
        char* buf,                  /* Buffer to read into. */
        size_t count,       /* Number of bytes to read. */
        size_t *pBytesRead)         /* Where to return number of
                                       bytes read. */
{
    ssize_t n;
    struct fd_data *fd_data;

    if ((fd_data = l_find_local_fd(fd)) != NULL) {
        if (L_FD_IS_VALID(fd_data)) {
            *pBytesRead = 0;
            return 1;
        }
    }
    for (;;)  {
        if ((n = read(fd, buf, count)) >= 0) {
            break;
        }
        else if (errno != EINTR) {
            return check_error(-1, errInfo);
        }
    }
    if (fd_data != NULL && L_FD_IS_VALID(fd_data)) {
        L_FD_INVALIDATE(fd_data);
    }
    *pBytesRead = (size_t) n;
    return 1;
}

/* pread() and pwrite()                                                   */
/* Some unix systems, notably Solaris has these syscalls                  */
/* It is especially nice for i.e. the dets module to have support         */
/* for this, even if the underlying OS dosn't support it, it is           */
/* reasonably easy to work around by first calling seek, and then         */
/* calling read().                                                        */
/* This later strategy however changes the file pointer, which pread()    */
/* does not do. We choose to ignore this and say that the location        */
/* of the file pointer is undefined after a call to any of the p functions*/


int
efile_pread(Efile_error* errInfo,     /* Where to return error codes. */
        int fd,                /* File descriptor to read from. */
        Sint64 offset,            /* Offset in bytes from BOF. */
        char* buf,                     /* Buffer to read into. */
        size_t count,          /* Number of bytes to read. */
        size_t *pBytesRead)            /* Where to return
                                          number of bytes read. */
{
    int res = efile_seek(errInfo, fd, offset, EFILE_SEEK_SET, NULL);
    if (res) {
        return efile_read(errInfo, 0, fd, buf, count, pBytesRead);
    } else {
        return res;
    }
}


/***************************************************************************/

int
efile_pwrite(Efile_error* errInfo,  /* Where to return error codes. */
        int fd,                /* File descriptor to write to. */
        char* buf,             /* Buffer to write. */
        size_t count,          /* Number of bytes to write. */
        Sint64 offset)         /* where to write it */
{
    int res = efile_seek(errInfo, fd, offset, EFILE_SEEK_SET, NULL);

    if (res) {
        return efile_write(errInfo, 0, fd, buf, count);
    } else {
        return res;
    }
}

/***************************************************************************/

int
efile_seek(Efile_error* errInfo,      /* Where to return error codes. */
        int fd,                    /* File descriptor to do the seek on. */
        Sint64 offset,             /* Offset in bytes from the given
                                      origin. */
        int origin,                /* Origin of seek (SEEK_SET, SEEK_CUR,
                                      SEEK_END). */
        Sint64 *new_location)      /* Resulting new location in file. */
{
    off_t off, result;
    off = (off_t) offset;

    switch (origin) {
        case EFILE_SEEK_SET:
            origin = SEEK_SET;
            break;
        case EFILE_SEEK_CUR:
            origin = SEEK_CUR;
            break;
        case EFILE_SEEK_END:
            origin = SEEK_END;
            break;
        default:
            errno = EINVAL;
            return check_error(-1, errInfo);
    }

    if (off != offset) {
        errno = EINVAL;
        return check_error(-1, errInfo);
    }

    errno = 0;
    result = lseek(fd, off, origin);

    if (result >= 0) {
        l_invalidate_local_fd(fd);
    }

    if (result < 0)
    {
        if (errno == ENOSYS) {
            int size, cur_pos;

            if (off < 0) {
                errno = EINVAL;
                return check_error(-1, errInfo);
            }

            cur_pos = lseek(fd, 0, SEEK_CUR);
            size = lseek(fd, 0, SEEK_END);

            if (origin == SEEK_SET) {
                result = offset;
            }
            else if (origin == SEEK_CUR) {
                result = offset + cur_pos;
            }
            else if (origin == SEEK_END) {
                result = size + offset;
            }

            /* sanity check our result */
            if (size > result) {
                return check_error(-1, errInfo);
            }

            /* store the data localy */
            l_update_local_fd(fd, result, size);

            /* reset the original file position */
            if (origin != SEEK_END) {
                lseek(fd, cur_pos, SEEK_SET);
            }
        }
        else if (errno == 0) {
            errno = EINVAL;
        }
    }

    if (new_location) {
        *new_location = result;
    }

    return 1;
}

/***************************************************************************/

int
efile_truncate_file(Efile_error* errInfo, int *fd, int flags)
{
    off_t offset;
    struct fd_data *fd_data;

    if ((fd_data = l_find_local_fd(*fd)) != NULL && L_FD_IS_VALID(fd_data)) {
        offset = L_FD_CUR(fd_data);
    }
    else {
        offset = lseek(*fd, 0, SEEK_CUR);
    }

    return check_error(((offset >= 0) &&
                (ftruncate(*fd, offset) == 0)) ? 1 : -1, errInfo);
}

/***************************************************************************/

int
efile_readlink(Efile_error* errInfo, char* name, char* buffer, size_t size)
{
    errno = ENOTSUP;
    return check_error(-1, errInfo);
}

/***************************************************************************/

int
efile_altname(Efile_error* errInfo, char* name, char* buffer, size_t size)
{
    errno = ENOTSUP;
    return check_error(-1, errInfo);
}

/***************************************************************************/

int
efile_link(Efile_error* errInfo, char* old, char* new)
{
    errno = ENOTSUP;
    return check_error(-1, errInfo);
}

/***************************************************************************/

int
efile_symlink(Efile_error* errInfo, char* old, char* new)
{
    errno = ENOTSUP;
    return check_error(-1, errInfo);
}

/***************************************************************************/

int
efile_fadvise(Efile_error* errInfo, int fd, Sint64 offset,
        Sint64 length, int advise)
{
    return check_error(posix_fadvise(fd, offset, length, advise), errInfo);
}

/***************************************************************************/

static int
call_posix_fallocate(int fd, Sint64 offset, Sint64 length)
{
    int ret;

    /*
     * On Linux and Solaris for example, posix_fallocate() returns
     * a positive error number on error and it does not set errno.
     * On FreeBSD however (9.0 at least), it returns -1 on error
     * and it sets errno.
     */
    do {
        ret = posix_fallocate(fd, (off_t) offset, (off_t) length);
        if (ret > 0) {
            errno = ret;
            ret = -1;
        }
    } while (ret != 0 && errno == EINTR);

    return ret;
}

/***************************************************************************/

int
efile_fallocate(Efile_error* errInfo, int fd, Sint64 offset, Sint64 length)
{
    return check_error(call_posix_fallocate(fd, offset, length), errInfo);
}
