/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * 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.
 */

#include "JNIHelp.h"
#include "AndroidSystemNatives.h"
#include "utils/misc.h"
#include <sys/mman.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#undef MMAP_READ_ONLY
#define MMAP_READ_ONLY 1L
#undef MMAP_READ_WRITE
#define MMAP_READ_WRITE 2L
#undef MMAP_WRITE_COPY
#define MMAP_WRITE_COPY 4L

/*
 * Class:     org_apache_harmony_luni_platform_OSMemory
 * Method:    littleEndian
 * Signature: ()Z
 */
static jboolean harmony_nio_littleEndian(JNIEnv *_env, jclass _this) {
    long l = 0x01020304;
    unsigned char* c = (unsigned char*)&l;
    return (*c == 0x04) ? JNI_TRUE : JNI_FALSE;
}

/*
 * Class:     org_apache_harmony_luni_platform_OSMemory
 * Method:    getPointerSizeImpl
 * Signature: ()I
 */
static jint harmony_nio_getPointerSizeImpl(JNIEnv *_env, jclass _this) {
    return sizeof(void *);
}

/*
 * Class:     org_apache_harmony_luni_platform_OSMemory
 * Method:    mallocImpl
 * Signature: (I)I
 */
static jint harmony_nio_mallocImpl(JNIEnv *_env, jobject _this, jint size) {
    void *returnValue = malloc(size);
    if(returnValue == NULL) {
        jniThrowException(_env, "java.lang.OutOfMemoryError", "");
    }
    return (jint)returnValue;
}

/*
 * Class:     org_apache_harmony_luni_platform_OSMemory
 * Method:    freeImpl
 * Signature: (I)V
 */
static void harmony_nio_freeImpl(JNIEnv *_env, jobject _this, jint pointer) {
    free((void *)pointer);
}

/*
 * Class:     org_apache_harmony_luni_platform_OSMemory
 * Method:    memset
 * Signature: (IBJ)V
 */
static void harmony_nio_memset(JNIEnv *_env, jobject _this, jint address, 
        jbyte value, jlong length) {
    memset ((void *) ((jint) address), (jbyte) value, (jlong) length);
}

/*
 * Class:     org_apache_harmony_luni_platform_OSMemory
 * Method:    memmove
 * Signature: (IIJ)V
 */
static void harmony_nio_memmove(JNIEnv *_env, jobject _this, jint destAddress, 
        jint srcAddress, jlong length) {
    memmove ((void *) ((jint) destAddress), (const void *) ((jint) srcAddress), 
        (jlong) length);
}

/*
 * Class:     org_apache_harmony_luni_platform_OSMemory
 * Method:    getByteImpl
 * Signature: (I)B
 */
static jbyte harmony_nio_getByteImpl(JNIEnv *_env, jobject _this, 
        jint pointer) {
    jbyte returnValue = *((jbyte *)pointer);
    return returnValue;
}

/*
 * Class:     org_apache_harmony_luni_platform_OSMemory
 * Method:    getBytesImpl
 * Signature: (I[BII)V
 */
static void harmony_nio_getBytesImpl(JNIEnv *_env, jobject _this, jint pointer, 
        jbyteArray dst, jint offset, jint length) {
    jbyte *dst_ = (jbyte *)_env->GetPrimitiveArrayCritical(dst, (jboolean *)0);
    memcpy(dst_ + offset, (jbyte *)pointer, length);
    _env->ReleasePrimitiveArrayCritical(dst, dst_, 0);
}

/*
 * Class:     org_apache_harmony_luni_platform_OSMemory
 * Method:    putByteImpl
 * Signature: (IB)V
 */
static void harmony_nio_putByteImpl(JNIEnv *_env, jobject _this, jint pointer,
        jbyte val) {
    *((jbyte *)pointer) = val;
}

/*
 * Class:     org_apache_harmony_luni_platform_OSMemory
 * Method:    putBytesImpl
 * Signature: (I[BII)V
 */
static void harmony_nio_putBytesImpl(JNIEnv *_env, jobject _this,
        jint pointer, jbyteArray src, jint offset, jint length) {
    jbyte *src_ = (jbyte *)_env->GetPrimitiveArrayCritical(src, (jboolean *)0);
    memcpy((jbyte *)pointer, src_ + offset, length);
    _env->ReleasePrimitiveArrayCritical(src, src_, JNI_ABORT);
}

static void
swapShorts(jshort *shorts, int numBytes) {
    jbyte *src = (jbyte *) shorts;
    jbyte *dst = src;
    int i;
    
    for (i = 0; i < numBytes; i+=2) {
        jbyte b0 = *src++;
        jbyte b1 = *src++;
        *dst++ = b1;
        *dst++ = b0;
    }
}

static void
swapInts(jint *ints, int numBytes) {
    jbyte *src = (jbyte *) ints;
    jbyte *dst = src;
    int i;   
    for (i = 0; i < numBytes; i+=4) {
        jbyte b0 = *src++;
        jbyte b1 = *src++;
        jbyte b2 = *src++;
        jbyte b3 = *src++;
        *dst++ = b3;
        *dst++ = b2;
        *dst++ = b1;
        *dst++ = b0;
    }
}

/*
 * Class:     org_apache_harmony_luni_platform_OSMemory
 * Method:    putShortsImpl
 * Signature: (I[SIIZ)V
 */
static void harmony_nio_putShortsImpl(JNIEnv *_env, jobject _this,
       jint pointer, jshortArray src, jint offset, jint length, jboolean swap) {
       
    offset = offset << 1;
    length = length << 1;
       
    jshort *src_ =
        (jshort *)_env->GetPrimitiveArrayCritical(src, (jboolean *)0);
    if (swap) {
        swapShorts(src_ + offset, length);
    }
    memcpy((jbyte *)pointer, (jbyte *)src_ + offset, length);
    if (swap) {
        swapShorts(src_ + offset, length);
    }
    _env->ReleasePrimitiveArrayCritical(src, src_, JNI_ABORT);
}

/*
 * Class:     org_apache_harmony_luni_platform_OSMemory
 * Method:    putIntsImpl
 * Signature: (I[IIIZ)V
 */
static void harmony_nio_putIntsImpl(JNIEnv *_env, jobject _this,
       jint pointer, jintArray src, jint offset, jint length, jboolean swap) {

    offset = offset << 2;
    length = length << 2;
       
    jint *src_ =
        (jint *)_env->GetPrimitiveArrayCritical(src, (jboolean *)0);
    if (swap) {
        swapInts(src_ + offset, length);
    }
    memcpy((jbyte *)pointer, (jbyte *)src_ + offset, length);
    if (swap) {
        swapInts(src_ + offset, length);
    }
    _env->ReleasePrimitiveArrayCritical(src, src_, JNI_ABORT);
}

/*
 * Class:     org_apache_harmony_luni_platform_OSMemory
 * Method:    getShortImpl
 * Signature: (I)S
 */
static jshort harmony_nio_getShortImpl(JNIEnv *_env, jobject _this, 
        jint pointer) {
    if ((pointer & 0x1) == 0) {
        jshort returnValue = *((jshort *)pointer);
        return returnValue;
    } else {
        // Handle unaligned memory access one byte at a time
        jshort s;
        unsigned char *src = (unsigned char *) pointer;
        unsigned char *dst = (unsigned char *) &s;
        dst[0] = src[0];
        dst[1] = src[1];
        return s;
    }
}

/*
 * Class:     org_apache_harmony_luni_platform_OSMemory
 * Method:    petShortImpl
 * Signature: (IS)V
 */
static void harmony_nio_putShortImpl(JNIEnv *_env, jobject _this, jint pointer, 
        jshort value) {
    if ((pointer & 0x1) == 0) {
        *((jshort *)pointer) = value;
    } else {
        // Handle unaligned memory access one byte at a time
        unsigned char *src = (unsigned char *) &value;
        unsigned char *dst = (unsigned char *) pointer;
        dst[0] = src[0];
        dst[1] = src[1];
    }
}

/*
 * Class:     org_apache_harmony_luni_platform_OSMemory
 * Method:    getIntImpl
 * Signature: (I)I
 */
static jint harmony_nio_getIntImpl(JNIEnv *_env, jobject _this, jint pointer) {
    if ((pointer & 0x3) == 0) {
        jint returnValue = *((jint *)pointer);
        return returnValue;
    } else {
        // Handle unaligned memory access one byte at a time
        jint i;
        unsigned char *src = (unsigned char *) pointer;
        unsigned char *dst = (unsigned char *) &i;
        dst[0] = src[0];
        dst[1] = src[1];
        dst[2] = src[2];
        dst[3] = src[3];
        return i;
    }
}

/*
 * Class:     org_apache_harmony_luni_platform_OSMemory
 * Method:    putIntImpl
 * Signature: (II)V
 */
static void harmony_nio_putIntImpl(JNIEnv *_env, jobject _this, jint pointer, 
        jint value) {
    if ((pointer & 0x3) == 0) {
        *((jint *)pointer) = value;
    } else {
        // Handle unaligned memory access one byte at a time
        unsigned char *src = (unsigned char *) &value;
        unsigned char *dst = (unsigned char *) pointer;
        dst[0] = src[0];
        dst[1] = src[1];
        dst[2] = src[2];
        dst[3] = src[3];
    }
}

/*
 * Class:     org_apache_harmony_luni_platform_OSMemory
 * Method:    getLongImpl
 * Signature: (I)Ljava/lang/Long;
 */
static jlong harmony_nio_getLongImpl(JNIEnv *_env, jobject _this, 
        jint pointer) {
    if ((pointer & 0x7) == 0) {
        jlong returnValue = *((jlong *)pointer);
        return returnValue;
    } else {
        // Handle unaligned memory access one byte at a time
        jlong l;
        memcpy((void *) &l, (void *) pointer, sizeof(jlong));
        return l;
    }
}

/*
 * Class:     org_apache_harmony_luni_platform_OSMemory
 * Method:    putLongImpl
 * Signature: (IJ)V
 */
static void harmony_nio_putLongImpl(JNIEnv *_env, jobject _this, jint pointer, 
        jlong value) {
    if ((pointer & 0x7) == 0) {
        *((jlong *)pointer) = value;
    } else {
        // Handle unaligned memory access one byte at a time
        memcpy((void *) pointer, (void *) &value, sizeof(jlong));
    }
}

/*
 * Class:     org_apache_harmony_luni_platform_OSMemory
 * Method:    getFloatImpl
 * Signature: (I)F
 */
static jfloat harmony_nio_getFloatImpl(JNIEnv *_env, jobject _this, 
        jint pointer) {
    if ((pointer & 0x3) == 0) {
        jfloat returnValue = *((jfloat *)pointer);
        return returnValue;
    } else {
        // Handle unaligned memory access one byte at a time
        jfloat f;
        memcpy((void *) &f, (void *) pointer, sizeof(jfloat));
        return f;
    }
}

/*
 * Class:     org_apache_harmony_luni_platform_OSMemory
 * Method:    setFloatImpl
 * Signature: (IF)V
 */
static void harmony_nio_putFloatImpl(JNIEnv *_env, jobject _this, jint pointer, 
        jfloat value) {
    if ((pointer & 0x3) == 0) {
        *((jfloat *)pointer) = value;
    } else {
        // Handle unaligned memory access one byte at a time
        memcpy((void *) pointer, (void *) &value, sizeof(jfloat));
    }
}

/*
 * Class:     org_apache_harmony_luni_platform_OSMemory
 * Method:    getDoubleImpl
 * Signature: (I)D
 */
static jdouble harmony_nio_getDoubleImpl(JNIEnv *_env, jobject _this, 
        jint pointer) {
    if ((pointer & 0x7) == 0) {
        jdouble returnValue = *((jdouble *)pointer);
        return returnValue;
    } else {
        // Handle unaligned memory access one byte at a time
        jdouble d;
        memcpy((void *) &d, (void *) pointer, sizeof(jdouble));
        return d;
    }
}

/*
 * Class:     org_apache_harmony_luni_platform_OSMemory
 * Method:    putDoubleImpl
 * Signature: (ID)V
 */
static void harmony_nio_putDoubleImpl(JNIEnv *_env, jobject _this, jint pointer, 
        jdouble value) {
    if ((pointer & 0x7) == 0) {
        *((jdouble *)pointer) = value;
    } else {
        // Handle unaligned memory access one byte at a time
        memcpy((void *) pointer, (void *) &value, sizeof(jdouble));
    }
}

/*
 * Class:     org_apache_harmony_luni_platform_OSMemory
 * Method:    getAddress
 * Signature: (I)I
 */
static jint harmony_nio_getAddress(JNIEnv *_env, jobject _this, jint pointer) {
    return (jint) * (int *) pointer;
}

/*
 * Class:     org_apache_harmony_luni_platform_OSMemory
 * Method:    setAddress
 * Signature: (II)V
 */
static void harmony_nio_setAddress(JNIEnv *_env, jobject _this, jint pointer, 
        jint value) {
    *(int *) pointer = (int) value;
}

/*
 * Class:     org_apache_harmony_luni_platform_OSMemory
 * Method:    mmapImpl
 * Signature: (IJJI)I
 */
static jint harmony_nio_mmapImpl(JNIEnv *_env, jobject _this, jint fd, 
        jlong alignment, jlong size, jint mmode) {
    void *mapAddress = NULL;
    int prot, flags;
          
    // Convert from Java mapping mode to port library mapping mode.
    switch (mmode) {
      case MMAP_READ_ONLY:
              prot = PROT_READ;
              flags = MAP_SHARED;
              break;
      case MMAP_READ_WRITE:
              prot = PROT_READ|PROT_WRITE;
              flags = MAP_SHARED;
              break;
      case MMAP_WRITE_COPY:
              prot = PROT_READ|PROT_WRITE;
              flags = MAP_PRIVATE;
              break;
      default:
              return -1;
    }

    mapAddress = mmap(0, (size_t)(size&0x7fffffff), prot, flags,fd,
            (off_t)(alignment&0x7fffffff));
    if (mapAddress == MAP_FAILED) {
        return -1;
    }
    
    return (jint) mapAddress;
}

/*
 * Class:     org_apache_harmony_luni_platform_OSMemory
 * Method:    unmapImpl
 * Signature: (IJ)V
 */
static void harmony_nio_unmapImpl(JNIEnv *_env, jobject _this, jint address, 
        jlong size) {
    munmap((void *)address, (size_t)size);
}

/*
 * Class:     org_apache_harmony_luni_platform_OSMemory
 * Method:    loadImpl
 * Signature: (IJ)I
 */
static jint harmony_nio_loadImpl(JNIEnv *_env, jobject _this, jint address, 
        jlong size) {

    if(mlock((void *)address, (size_t)size)!=-1) {
        if(munlock((void *)address, (size_t)size)!=-1) {
              return 0;  /* normally */
        }
    }
    else {
         /* according to linux sys call, only root can mlock memory. */
         if(errno == EPERM) {
             return 0;
         }
    }
    
    return -1;
}

int getPageSize() {
    static int page_size = 0;
    if(page_size==0)
    {
        page_size=getpagesize();
    }
    return page_size;
}

/*
 * Class:     org_apache_harmony_luni_platform_OSMemory
 * Method:    isLoadedImpl
 * Signature: (IJ)Z
 */
static jboolean harmony_nio_isLoadedImpl(JNIEnv *_env, jobject _this, 
        jint address, jlong size) {

    jboolean result = 0;
    jint m_addr = (jint)address;
    int page_size = getPageSize();
    unsigned char* vec = NULL;
    int page_count = 0;
    
    int align_offset = m_addr%page_size;// addr should align with the boundary of a page.
    m_addr -= align_offset;
    size   += align_offset;
    page_count = (size+page_size-1)/page_size;
    
    vec = (unsigned char *) malloc(page_count*sizeof(char));
    
    if (mincore((void *)m_addr, size, (MINCORE_POINTER_TYPE) vec)==0) {
        // or else there is error about the mincore and return false;
        int i;
        for(i=0 ;i<page_count;i++) {
            if(vec[i]!=1) {
                break;
            }
        }
        if(i==page_count) {
            result = 1;
        }
    }
    
    free(vec);
    
    return result;
}

/*
 * Class:     org_apache_harmony_luni_platform_OSMemory
 * Method:    flushImpl
 * Signature: (IJ)I
 */
static jint harmony_nio_flushImpl(JNIEnv *_env, jobject _this, jint address, 
        jlong size) {
    return msync((void *)address, size, MS_SYNC);
}

/*
 * JNI registration
 */
static JNINativeMethod gMethods[] = {
    /* name, signature, funcPtr */
    { "isLittleEndianImpl", "()Z",     (void*) harmony_nio_littleEndian },
    { "getPointerSizeImpl", "()I",     (void*) harmony_nio_getPointerSizeImpl },
    { "malloc",             "(I)I",    (void*) harmony_nio_mallocImpl },
    { "free",               "(I)V",    (void*) harmony_nio_freeImpl },
    { "memset",             "(IBJ)V",  (void*) harmony_nio_memset },
    { "memmove",            "(IIJ)V",  (void*) harmony_nio_memmove },
    { "getByteArray",       "(I[BII)V",(void*) harmony_nio_getBytesImpl },
    { "setByteArray",       "(I[BII)V",(void*) harmony_nio_putBytesImpl },
    { "setShortArray",     "(I[SIIZ)V",(void*) harmony_nio_putShortsImpl },
    { "setIntArray",       "(I[IIIZ)V",(void*) harmony_nio_putIntsImpl },
    { "getByte",            "(I)B",    (void*) harmony_nio_getByteImpl },
    { "setByte",            "(IB)V",   (void*) harmony_nio_putByteImpl },
    { "getShort",           "(I)S",    (void*) harmony_nio_getShortImpl },
    { "setShort",           "(IS)V",   (void*) harmony_nio_putShortImpl },
    { "getInt",             "(I)I",    (void*) harmony_nio_getIntImpl },
    { "setInt",             "(II)V",   (void*) harmony_nio_putIntImpl },
    { "getLong",            "(I)J",    (void*) harmony_nio_getLongImpl },
    { "setLong",            "(IJ)V",   (void*) harmony_nio_putLongImpl },
    { "getFloat",           "(I)F",    (void*) harmony_nio_getFloatImpl },
    { "setFloat",           "(IF)V",   (void*) harmony_nio_putFloatImpl },
    { "getDouble",          "(I)D",    (void*) harmony_nio_getDoubleImpl },
    { "setDouble",          "(ID)V",   (void*) harmony_nio_putDoubleImpl },
    { "getAddress",         "(I)I",    (void*) harmony_nio_getAddress },
    { "setAddress",         "(II)V",   (void*) harmony_nio_setAddress },
    { "mmapImpl",           "(IJJI)I", (void*) harmony_nio_mmapImpl },
    { "unmapImpl",          "(IJ)V",   (void*) harmony_nio_unmapImpl },
    { "loadImpl",           "(IJ)I",   (void*) harmony_nio_loadImpl },
    { "isLoadedImpl",       "(IJ)Z",   (void*) harmony_nio_isLoadedImpl },
    { "flushImpl",          "(IJ)I",   (void*) harmony_nio_flushImpl }
};
int register_org_apache_harmony_luni_platform_OSMemory(JNIEnv *_env) {
    return jniRegisterNativeMethods(_env, "org/apache/harmony/luni/platform/OSMemory",
                gMethods, NELEM(gMethods));
}
