blob: d32767c0b2d9201a50d27d0f05a1c54daccf0554 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
// BEGIN android-note
// This file corresponds to harmony's OSFileSystem.c and OSFileSystemLinux32.c.
// It has been greatly simplified by the assumption that the underlying
// platform is always Linux.
// END android-note
/*
* Common natives supporting the file system interface.
*/
#define HyMaxPath 1024
/* Values for HyFileOpen */
#define HyOpenRead 1
#define HyOpenWrite 2
#define HyOpenCreate 4
#define HyOpenTruncate 8
#define HyOpenAppend 16
#define HyOpenText 32
/* Use this flag with HyOpenCreate, if this flag is specified then
* trying to create an existing file will fail
*/
#define HyOpenCreateNew 64
#define HyOpenSync 128
#define SHARED_LOCK_TYPE 1L
#include "JNIHelp.h"
#include "AndroidSystemNatives.h"
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/sendfile.h>
#include <sys/uio.h>
// An equivalent of the glibc macro of the same name.
// We want to hide EINTR from Java by simply retrying directly in
// the native code. We care about all other errors, though.
#define EINTR_RETRY(exp) ({ \
typeof (exp) _rc; \
do { \
_rc = (exp); \
} while (_rc == -1 && errno == EINTR); \
_rc; })
static void convertToPlatform(char *path) {
char *pathIndex;
pathIndex = path;
while (*pathIndex != '\0') {
if (*pathIndex == '\\') {
*pathIndex = '/';
}
pathIndex++;
}
}
static int EsTranslateOpenFlags(int flags) {
int realFlags = 0;
if (flags & HyOpenAppend) {
realFlags |= O_APPEND;
}
if (flags & HyOpenTruncate) {
realFlags |= O_TRUNC;
}
if (flags & HyOpenCreate) {
realFlags |= O_CREAT;
}
if (flags & HyOpenCreateNew) {
realFlags |= O_EXCL | O_CREAT;
}
#ifdef O_SYNC
if (flags & HyOpenSync) {
realFlags |= O_SYNC;
}
#endif
if (flags & HyOpenRead) {
if (flags & HyOpenWrite) {
return (O_RDWR | realFlags);
}
return (O_RDONLY | realFlags);
}
if (flags & HyOpenWrite) {
return (O_WRONLY | realFlags);
}
return -1;
}
// Checks whether we can safely treat the given jlong as an off_t without
// accidental loss of precision.
// TODO: this is bogus; we should use _FILE_OFFSET_BITS=64.
static bool offsetTooLarge(JNIEnv* env, jlong longOffset) {
if (sizeof(off_t) >= sizeof(jlong)) {
// We're only concerned about the possibility that off_t is
// smaller than jlong. off_t is signed, so we don't need to
// worry about signed/unsigned.
return false;
}
// TODO: use std::numeric_limits<off_t>::max() and min() when we have them.
assert(sizeof(off_t) == sizeof(int));
static const off_t off_t_max = INT_MAX;
static const off_t off_t_min = INT_MIN;
if (longOffset > off_t_max || longOffset < off_t_min) {
// "Value too large for defined data type".
jniThrowIOException(env, EOVERFLOW);
return true;
}
return false;
}
static jlong translateLockLength(jlong length) {
// FileChannel.tryLock uses Long.MAX_VALUE to mean "lock the whole
// file", where POSIX would use 0. We can support that special case,
// even for files whose actual length we can't represent. For other
// out of range lengths, though, we want our range checking to fire.
return (length == 0x7fffffffffffffffLL) ? 0 : length;
}
static struct flock flockFromStartAndLength(jlong start, jlong length) {
struct flock lock;
memset(&lock, 0, sizeof(lock));
lock.l_whence = SEEK_SET;
lock.l_start = start;
lock.l_len = length;
return lock;
}
static jint harmony_io_lockImpl(JNIEnv* env, jobject, jint handle,
jlong start, jlong length, jint typeFlag, jboolean waitFlag) {
length = translateLockLength(length);
if (offsetTooLarge(env, start) || offsetTooLarge(env, length)) {
return -1;
}
struct flock lock(flockFromStartAndLength(start, length));
if ((typeFlag & SHARED_LOCK_TYPE) == SHARED_LOCK_TYPE) {
lock.l_type = F_RDLCK;
} else {
lock.l_type = F_WRLCK;
}
int waitMode = (waitFlag) ? F_SETLKW : F_SETLK;
return EINTR_RETRY(fcntl(handle, waitMode, &lock));
}
static void harmony_io_unlockImpl(JNIEnv* env, jobject, jint handle,
jlong start, jlong length) {
length = translateLockLength(length);
if (offsetTooLarge(env, start) || offsetTooLarge(env, length)) {
return;
}
struct flock lock(flockFromStartAndLength(start, length));
lock.l_type = F_UNLCK;
int rc = EINTR_RETRY(fcntl(handle, F_SETLKW, &lock));
if (rc == -1) {
jniThrowIOException(env, errno);
}
}
/**
* Returns the granularity of the starting address for virtual memory allocation.
* (It's the same as the page size.)
*/
static jint harmony_io_getAllocGranularity(JNIEnv* env, jobject) {
static int allocGranularity = getpagesize();
return allocGranularity;
}
static jlong harmony_io_readv(JNIEnv* env, jobject, jint fd,
jintArray jBuffers, jintArray jOffsets, jintArray jLengths, jint size) {
iovec* vectors = new iovec[size];
if (vectors == NULL) {
jniThrowException(env, "java/lang/OutOfMemoryError", "native heap");
return -1;
}
jint *buffers = env->GetIntArrayElements(jBuffers, NULL);
jint *offsets = env->GetIntArrayElements(jOffsets, NULL);
jint *lengths = env->GetIntArrayElements(jLengths, NULL);
for (int i = 0; i < size; ++i) {
vectors[i].iov_base = (void *)((int)(buffers[i]+offsets[i]));
vectors[i].iov_len = lengths[i];
}
long result = readv(fd, vectors, size);
env->ReleaseIntArrayElements(jBuffers, buffers, JNI_ABORT);
env->ReleaseIntArrayElements(jOffsets, offsets, JNI_ABORT);
env->ReleaseIntArrayElements(jLengths, lengths, JNI_ABORT);
delete[] vectors;
if (result == -1) {
jniThrowIOException(env, errno);
}
return result;
}
static jlong harmony_io_writev(JNIEnv* env, jobject, jint fd,
jintArray jBuffers, jintArray jOffsets, jintArray jLengths, jint size) {
iovec* vectors = new iovec[size];
if (vectors == NULL) {
jniThrowException(env, "java/lang/OutOfMemoryError", "native heap");
return -1;
}
jint *buffers = env->GetIntArrayElements(jBuffers, NULL);
jint *offsets = env->GetIntArrayElements(jOffsets, NULL);
jint *lengths = env->GetIntArrayElements(jLengths, NULL);
for (int i = 0; i < size; ++i) {
vectors[i].iov_base = (void *)((int)(buffers[i]+offsets[i]));
vectors[i].iov_len = lengths[i];
}
long result = writev(fd, vectors, size);
env->ReleaseIntArrayElements(jBuffers, buffers, JNI_ABORT);
env->ReleaseIntArrayElements(jOffsets, offsets, JNI_ABORT);
env->ReleaseIntArrayElements(jLengths, lengths, JNI_ABORT);
delete[] vectors;
if (result == -1) {
jniThrowIOException(env, errno);
}
return result;
}
static jlong harmony_io_transfer(JNIEnv* env, jobject, jint fd, jobject sd,
jlong offset, jlong count) {
int socket = jniGetFDFromFileDescriptor(env, sd);
if (socket == -1) {
return -1;
}
/* Value of offset is checked in jint scope (checked in java layer)
The conversion here is to guarantee no value lost when converting offset to off_t
*/
off_t off = offset;
ssize_t rc = sendfile(socket, fd, &off, count);
if (rc == -1) {
jniThrowIOException(env, errno);
}
return rc;
}
static jlong harmony_io_readDirect(JNIEnv* env, jobject, jint fd,
jint buf, jint offset, jint nbytes) {
if (nbytes == 0) {
return 0;
}
jbyte* dst = reinterpret_cast<jbyte*>(buf + offset);
jlong rc = EINTR_RETRY(read(fd, dst, nbytes));
if (rc == 0) {
return -1;
}
if (rc == -1) {
jniThrowIOException(env, errno);
}
return rc;
}
static jlong harmony_io_writeDirect(JNIEnv* env, jobject, jint fd,
jint buf, jint offset, jint nbytes) {
jbyte* src = reinterpret_cast<jbyte*>(buf + offset);
jlong rc = EINTR_RETRY(write(fd, src, nbytes));
if (rc == -1) {
jniThrowIOException(env, errno);
}
return rc;
}
static jlong harmony_io_readImpl(JNIEnv* env, jobject, jint fd,
jbyteArray byteArray, jint offset, jint nbytes) {
if (nbytes == 0) {
return 0;
}
jbyte* bytes = env->GetByteArrayElements(byteArray, NULL);
jlong rc = EINTR_RETRY(read(fd, bytes + offset, nbytes));
env->ReleaseByteArrayElements(byteArray, bytes, 0);
if (rc == 0) {
return -1;
}
if (rc == -1) {
if (errno == EAGAIN) {
jniThrowException(env, "java/io/InterruptedIOException",
"Read timed out");
} else {
jniThrowIOException(env, errno);
}
}
return rc;
}
static jlong harmony_io_writeImpl(JNIEnv* env, jobject, jint fd,
jbyteArray byteArray, jint offset, jint nbytes) {
jbyte* bytes = env->GetByteArrayElements(byteArray, NULL);
jlong result = EINTR_RETRY(write(fd, bytes + offset, nbytes));
env->ReleaseByteArrayElements(byteArray, bytes, JNI_ABORT);
if (result == -1) {
if (errno == EAGAIN) {
jniThrowException(env, "java/io/InterruptedIOException",
"Write timed out");
} else {
jniThrowIOException(env, errno);
}
}
return result;
}
static jlong harmony_io_seek(JNIEnv* env, jobject, jint fd, jlong offset,
jint javaWhence) {
/* Convert whence argument */
int nativeWhence = 0;
switch (javaWhence) {
case 1:
nativeWhence = SEEK_SET;
break;
case 2:
nativeWhence = SEEK_CUR;
break;
case 4:
nativeWhence = SEEK_END;
break;
default:
return -1;
}
// If the offset is relative, lseek(2) will tell us whether it's too large.
// We're just worried about too large an absolute offset, which would cause
// us to lie to lseek(2).
if (offsetTooLarge(env, offset)) {
return -1;
}
jlong result = lseek(fd, offset, nativeWhence);
if (result == -1) {
jniThrowIOException(env, errno);
}
return result;
}
// TODO: are we supposed to support the 'metadata' flag? (false => fdatasync.)
static void harmony_io_fflush(JNIEnv* env, jobject, jint fd,
jboolean metadata) {
int rc = fsync(fd);
if (rc == -1) {
jniThrowIOException(env, errno);
}
}
static jint harmony_io_close(JNIEnv* env, jobject, jint fd) {
jint rc = EINTR_RETRY(close(fd));
if (rc == -1) {
jniThrowIOException(env, errno);
}
return rc;
}
static jint harmony_io_truncate(JNIEnv* env, jobject, jint fd, jlong length) {
if (offsetTooLarge(env, length)) {
return -1;
}
int rc = ftruncate(fd, length);
if (rc == -1) {
jniThrowIOException(env, errno);
}
return rc;
}
static jint harmony_io_openImpl(JNIEnv* env, jobject, jbyteArray path,
jint jflags) {
int flags = 0;
int mode = 0;
// BEGIN android-changed
// don't want default permissions to allow global access.
switch(jflags) {
case 0:
flags = HyOpenRead;
mode = 0;
break;
case 1:
flags = HyOpenCreate | HyOpenWrite | HyOpenTruncate;
mode = 0600;
break;
case 16:
flags = HyOpenRead | HyOpenWrite | HyOpenCreate;
mode = 0600;
break;
case 32:
flags = HyOpenRead | HyOpenWrite | HyOpenCreate | HyOpenSync;
mode = 0600;
break;
case 256:
flags = HyOpenWrite | HyOpenCreate | HyOpenAppend;
mode = 0600;
break;
}
// BEGIN android-changed
flags = EsTranslateOpenFlags(flags);
// TODO: clean this up when we clean up the java.io.File equivalent.
jsize length = env->GetArrayLength (path);
length = length < HyMaxPath - 1 ? length : HyMaxPath - 1;
char pathCopy[HyMaxPath];
env->GetByteArrayRegion (path, 0, length, (jbyte *)pathCopy);
pathCopy[length] = '\0';
convertToPlatform (pathCopy);
jint cc = EINTR_RETRY(open(pathCopy, flags, mode));
// TODO: chase up the callers of this and check they wouldn't rather
// have us throw a meaningful IOException right here.
if (cc < 0 && errno > 0) {
cc = -errno;
}
return cc;
}
static jint harmony_io_ioctlAvailable(JNIEnv*env, jobject, jint fd) {
/*
* On underlying platforms Android cares about (read "Linux"),
* ioctl(fd, FIONREAD, &avail) is supposed to do the following:
*
* If the fd refers to a regular file, avail is set to
* the difference between the file size and the current cursor.
* This may be negative if the cursor is past the end of the file.
*
* If the fd refers to an open socket or the read end of a
* pipe, then avail will be set to a number of bytes that are
* available to be read without blocking.
*
* If the fd refers to a special file/device that has some concept
* of buffering, then avail will be set in a corresponding way.
*
* If the fd refers to a special device that does not have any
* concept of buffering, then the ioctl call will return a negative
* number, and errno will be set to ENOTTY.
*
* If the fd refers to a special file masquerading as a regular file,
* then avail may be returned as negative, in that the special file
* may appear to have zero size and yet a previous read call may have
* actually read some amount of data and caused the cursor to be
* advanced.
*/
int avail = 0;
int rc = ioctl(fd, FIONREAD, &avail);
if (rc >= 0) {
/*
* Success, but make sure not to return a negative number (see
* above).
*/
if (avail < 0) {
avail = 0;
}
} else if (errno == ENOTTY) {
/* The fd is unwilling to opine about its read buffer. */
avail = 0;
} else {
/* Something strange is happening. */
jniThrowIOException(env, errno);
}
return (jint) avail;
}
static jlong harmony_io_ttyReadImpl(JNIEnv* env, jobject thiz,
jbyteArray byteArray, jint offset, jint nbytes) {
return harmony_io_readImpl(env, thiz, STDIN_FILENO, byteArray, offset, nbytes);
}
/*
* JNI registration
*/
static JNINativeMethod gMethods[] = {
/* name, signature, funcPtr */
{ "close", "(I)V", (void*) harmony_io_close },
{ "fflush", "(IZ)V", (void*) harmony_io_fflush },
{ "getAllocGranularity","()I", (void*) harmony_io_getAllocGranularity },
{ "ioctlAvailable", "(I)I", (void*) harmony_io_ioctlAvailable },
{ "lockImpl", "(IJJIZ)I", (void*) harmony_io_lockImpl },
{ "openImpl", "([BI)I", (void*) harmony_io_openImpl },
{ "readDirect", "(IIII)J", (void*) harmony_io_readDirect },
{ "readImpl", "(I[BII)J", (void*) harmony_io_readImpl },
{ "readv", "(I[I[I[II)J",(void*) harmony_io_readv },
{ "seek", "(IJI)J", (void*) harmony_io_seek },
{ "transfer", "(ILjava/io/FileDescriptor;JJ)J",
(void*) harmony_io_transfer },
{ "truncate", "(IJ)V", (void*) harmony_io_truncate },
{ "ttyReadImpl", "([BII)J", (void*) harmony_io_ttyReadImpl },
{ "unlockImpl", "(IJJ)V", (void*) harmony_io_unlockImpl },
{ "writeDirect", "(IIII)J", (void*) harmony_io_writeDirect },
{ "writeImpl", "(I[BII)J", (void*) harmony_io_writeImpl },
{ "writev", "(I[I[I[II)J",(void*) harmony_io_writev },
};
int register_org_apache_harmony_luni_platform_OSFileSystem(JNIEnv* _env) {
return jniRegisterNativeMethods(_env,
"org/apache/harmony/luni/platform/OSFileSystem", gMethods,
NELEM(gMethods));
}