/*
 * Copyright (c) 2000, 2007, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code 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 General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

#include "jni.h"
#include "jni_util.h"
#include "jvm.h"
#include "jlong.h"
#include <sys/mman.h>
#include <sys/stat.h>
#include "sun_nio_ch_FileChannelImpl.h"
#include "java_lang_Integer.h"
#include "nio.h"
#include "nio_util.h"
#include <dlfcn.h>

static jfieldID chan_fd;        /* jobject 'fd' in sun.io.FileChannelImpl */

#ifdef __solaris__
typedef struct sendfilevec64 {
    int     sfv_fd;         /* input fd */
    uint_t  sfv_flag;       /* Flags. see below */
    off64_t sfv_off;        /* offset to start reading from */
    size_t  sfv_len;        /* amount of data */
} sendfilevec_t;

/* Function pointer for sendfilev on Solaris 8+ */
typedef ssize_t sendfile_func(int fildes, const struct sendfilevec64 *vec,
                              int sfvcnt, size_t *xferred);

sendfile_func* my_sendfile_func = NULL;
#endif

#ifdef __linux__
#include <sys/sendfile.h>

/* Function pointer for sendfile64 on Linux 2.6 (and newer 2.4 kernels) */
typedef ssize_t sendfile64_func(int out_fd, int in_fd, off64_t *offset, size_t count);

sendfile64_func* my_sendfile64_func = NULL;
#endif

JNIEXPORT jlong JNICALL
Java_sun_nio_ch_FileChannelImpl_initIDs(JNIEnv *env, jclass clazz)
{
    jlong pageSize = sysconf(_SC_PAGESIZE);
    chan_fd = (*env)->GetFieldID(env, clazz, "fd", "Ljava/io/FileDescriptor;");

#ifdef __solaris__
    if (dlopen("/usr/lib/libsendfile.so.1", RTLD_GLOBAL | RTLD_LAZY) != NULL) {
        my_sendfile_func = (sendfile_func*) dlsym(RTLD_DEFAULT, "sendfilev64");
    }
#endif

#ifdef __linux__
    my_sendfile64_func = (sendfile64_func*) dlsym(RTLD_DEFAULT, "sendfile64");
#endif

    return pageSize;
}

static jlong
handle(JNIEnv *env, jlong rv, char *msg)
{
    if (rv >= 0)
        return rv;
    if (errno == EINTR)
        return IOS_INTERRUPTED;
    JNU_ThrowIOExceptionWithLastError(env, msg);
    return IOS_THROWN;
}


JNIEXPORT jlong JNICALL
Java_sun_nio_ch_FileChannelImpl_map0(JNIEnv *env, jobject this,
                                     jint prot, jlong off, jlong len)
{
    void *mapAddress = 0;
    jobject fdo = (*env)->GetObjectField(env, this, chan_fd);
    jint fd = fdval(env, fdo);
    int protections = 0;
    int flags = 0;

    if (prot == sun_nio_ch_FileChannelImpl_MAP_RO) {
        protections = PROT_READ;
        flags = MAP_SHARED;
    } else if (prot == sun_nio_ch_FileChannelImpl_MAP_RW) {
        protections = PROT_WRITE | PROT_READ;
        flags = MAP_SHARED;
    } else if (prot == sun_nio_ch_FileChannelImpl_MAP_PV) {
        protections =  PROT_WRITE | PROT_READ;
        flags = MAP_PRIVATE;
    }

    mapAddress = mmap64(
        0,                    /* Let OS decide location */
        len,                  /* Number of bytes to map */
        protections,          /* File permissions */
        flags,                /* Changes are shared */
        fd,                   /* File descriptor of mapped file */
        off);                 /* Offset into file */

    if (mapAddress == MAP_FAILED) {
        if (errno == ENOMEM) {
            JNU_ThrowOutOfMemoryError(env, "Map failed");
            return IOS_THROWN;
        }
        return handle(env, -1, "Map failed");
    }

    return ((jlong) (unsigned long) mapAddress);
}


JNIEXPORT jint JNICALL
Java_sun_nio_ch_FileChannelImpl_unmap0(JNIEnv *env, jobject this,
                                       jlong address, jlong len)
{
    void *a = (void *)jlong_to_ptr(address);
    return handle(env,
                  munmap(a, (size_t)len),
                  "Unmap failed");
}


JNIEXPORT jint JNICALL
Java_sun_nio_ch_FileChannelImpl_truncate0(JNIEnv *env, jobject this,
                                          jobject fdo, jlong size)
{
    return handle(env,
                  ftruncate64(fdval(env, fdo), size),
                  "Truncation failed");
}


JNIEXPORT jint JNICALL
Java_sun_nio_ch_FileChannelImpl_force0(JNIEnv *env, jobject this,
                                       jobject fdo, jboolean md)
{
    jint fd = fdval(env, fdo);
    int result = 0;

    if (md == JNI_FALSE) {
        result = fdatasync(fd);
    } else {
        result = fsync(fd);
    }
    return handle(env, result, "Force failed");
}


JNIEXPORT jlong JNICALL
Java_sun_nio_ch_FileChannelImpl_position0(JNIEnv *env, jobject this,
                                          jobject fdo, jlong offset)
{
    jint fd = fdval(env, fdo);
    jlong result = 0;

    if (offset < 0) {
        result = lseek64(fd, 0, SEEK_CUR);
    } else {
        result = lseek64(fd, offset, SEEK_SET);
    }
    return handle(env, result, "Position failed");
}


JNIEXPORT jlong JNICALL
Java_sun_nio_ch_FileChannelImpl_size0(JNIEnv *env, jobject this, jobject fdo)
{
    struct stat64 fbuf;

    if (fstat64(fdval(env, fdo), &fbuf) < 0)
        return handle(env, -1, "Size failed");
    return fbuf.st_size;
}


JNIEXPORT void JNICALL
Java_sun_nio_ch_FileChannelImpl_close0(JNIEnv *env, jobject this, jobject fdo)
{
    jint fd = fdval(env, fdo);
    if (fd != -1) {
        jlong result = close(fd);
        if (result < 0) {
            JNU_ThrowIOExceptionWithLastError(env, "Close failed");
        }
    }
}

JNIEXPORT jlong JNICALL
Java_sun_nio_ch_FileChannelImpl_transferTo0(JNIEnv *env, jobject this,
                                            jint srcFD,
                                            jlong position, jlong count,
                                            jint dstFD)
{
#ifdef __linux__
    jlong max = (jlong)java_lang_Integer_MAX_VALUE;
    jlong n;

    if (my_sendfile64_func == NULL) {
        off_t offset;
        if (position > max)
            return IOS_UNSUPPORTED_CASE;
        if (count > max)
            count = max;
        offset = (off_t)position;
        n = sendfile(dstFD, srcFD, &offset, (size_t)count);
    } else {
        off64_t offset = (off64_t)position;
        n = (*my_sendfile64_func)(dstFD, srcFD, &offset, (size_t)count);
    }
    if (n < 0) {
        if (errno == EAGAIN)
            return IOS_UNAVAILABLE;
        if ((errno == EINVAL) && ((ssize_t)count >= 0))
            return IOS_UNSUPPORTED_CASE;
        if (errno == EINTR) {
            return IOS_INTERRUPTED;
        }
        JNU_ThrowIOExceptionWithLastError(env, "Transfer failed");
        return IOS_THROWN;
    }
    return n;
#endif

#ifdef __solaris__
    if (my_sendfile_func == NULL) {
        return IOS_UNSUPPORTED;
    } else {
        sendfilevec_t sfv;
        size_t numBytes = 0;
        jlong result;

        sfv.sfv_fd = srcFD;
        sfv.sfv_flag = 0;
        sfv.sfv_off = (off64_t)position;
        sfv.sfv_len = count;

        result = (*my_sendfile_func)(dstFD, &sfv, 1, &numBytes);

        /* Solaris sendfilev() will return -1 even if some bytes have been
         * transferred, so we check numBytes first.
         */
        if (numBytes > 0)
            return numBytes;
        if (result < 0) {
            if (errno == EAGAIN)
                return IOS_UNAVAILABLE;
            if ((errno == EINVAL) && ((ssize_t)count >= 0))
                return IOS_UNSUPPORTED_CASE;
            if (errno == EINTR)
                return IOS_INTERRUPTED;
            JNU_ThrowIOExceptionWithLastError(env, "Transfer failed");
            return IOS_THROWN;
        }
        return result;
    }
#endif
}

JNIEXPORT jint JNICALL
Java_sun_nio_ch_FileChannelImpl_lock0(JNIEnv *env, jobject this, jobject fdo,
                                      jboolean block, jlong pos, jlong size,
                                      jboolean shared)
{
    jint fd = fdval(env, fdo);
    jint lockResult = 0;
    int cmd = 0;
    struct flock64 fl;

    fl.l_whence = SEEK_SET;
    fl.l_len = (off64_t)size;
    fl.l_start = (off64_t)pos;
    if (shared == JNI_TRUE) {
        fl.l_type = F_RDLCK;
    } else {
        fl.l_type = F_WRLCK;
    }
    if (block == JNI_TRUE) {
        cmd = F_SETLKW64;
    } else {
        cmd = F_SETLK64;
    }
    lockResult = fcntl(fd, cmd, &fl);
    if (lockResult < 0) {
        if ((cmd == F_SETLK64) && (errno == EAGAIN))
            return sun_nio_ch_FileChannelImpl_NO_LOCK;
        if (errno == EINTR)
            return sun_nio_ch_FileChannelImpl_INTERRUPTED;
        JNU_ThrowIOExceptionWithLastError(env, "Lock failed");
    }
    return 0;
}


JNIEXPORT void JNICALL
Java_sun_nio_ch_FileChannelImpl_release0(JNIEnv *env, jobject this,
                                         jobject fdo, jlong pos, jlong size)
{
    jint fd = fdval(env, fdo);
    jint lockResult = 0;
    struct flock64 fl;
    int cmd = F_SETLK64;

    fl.l_whence = SEEK_SET;
    fl.l_len = (off64_t)size;
    fl.l_start = (off64_t)pos;
    fl.l_type = F_UNLCK;
    lockResult = fcntl(fd, cmd, &fl);
    if (lockResult < 0) {
        JNU_ThrowIOExceptionWithLastError(env, "Release failed");
    }
}
