blob: bd81361027b672c3606d9ba5028c77d4067eaf23 [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.
*/
#include "AndroidSystemNatives.h"
#include "JNIHelp.h"
#include "LocalArray.h"
#include "ScopedByteArray.h"
#include "ScopedFd.h"
#include <string.h>
#include <fcntl.h>
#include <time.h>
#include <utime.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <errno.h>
// BEGIN android-note: this file has been extensively rewritten to
// remove fixed-length buffers, buffer overruns, duplication, and
// poor choices of where to divide the work between Java and native
// code.
static jbyteArray java_io_File_getCanonImpl(JNIEnv* env, jobject, jbyteArray pathBytes) {
ScopedByteArray path(env, pathBytes);
// The only thing this native code currently does is truncate the byte[] at
// the first NUL.
// TODO: this is completely pointless. we should do this in Java, or do all of getCanonicalPath in native code. (realpath(2)?)
size_t length = strlen(&path[0]);
jbyteArray result = env->NewByteArray(length);
env->SetByteArrayRegion(result, 0, length, path.bytes());
return result;
}
static jboolean java_io_File_deleteImpl(JNIEnv* env, jobject, jbyteArray pathBytes) {
ScopedByteArray path(env, pathBytes);
return (remove(&path[0]) == 0);
}
static bool doStat(JNIEnv* env, jbyteArray pathBytes, struct stat& sb) {
ScopedByteArray path(env, pathBytes);
return (stat(&path[0], &sb) == 0);
}
static jlong java_io_File_lengthImpl(JNIEnv* env, jobject, jbyteArray pathBytes) {
struct stat sb;
if (!doStat(env, pathBytes, sb)) {
// We must return 0 for files that don't exist.
// TODO: shouldn't we throw an IOException for ELOOP or EACCES?
return 0;
}
/*
* This android-changed code explicitly treats non-regular files (e.g.,
* sockets and block-special devices) as having size zero. Some synthetic
* "regular" files may report an arbitrary non-zero size, but
* in these cases they generally report a block count of zero.
* So, use a zero block count to trump any other concept of
* size.
*
* TODO: why do we do this?
*/
if (!S_ISREG(sb.st_mode) || sb.st_blocks == 0) {
return 0;
}
return sb.st_size;
}
static jlong java_io_File_lastModifiedImpl(JNIEnv* env, jobject, jbyteArray pathBytes) {
struct stat sb;
if (!doStat(env, pathBytes, sb)) {
return 0;
}
return static_cast<jlong>(sb.st_mtime) * 1000L;
}
static jboolean java_io_File_isDirectoryImpl(JNIEnv* env, jobject, jbyteArray pathBytes) {
struct stat sb;
return (doStat(env, pathBytes, sb) && S_ISDIR(sb.st_mode));
}
static jboolean java_io_File_isFileImpl(JNIEnv* env, jobject, jbyteArray pathBytes) {
struct stat sb;
return (doStat(env, pathBytes, sb) && S_ISREG(sb.st_mode));
}
static jboolean java_io_File_existsImpl(JNIEnv* env, jobject, jbyteArray pathBytes) {
ScopedByteArray path(env, pathBytes);
return (access(&path[0], F_OK) == 0);
}
static jboolean java_io_File_isReadableImpl(JNIEnv* env, jobject, jbyteArray pathBytes) {
ScopedByteArray path(env, pathBytes);
return (access(&path[0], R_OK) == 0);
}
static jboolean java_io_File_isWritableImpl(JNIEnv* env, jobject recv, jbyteArray pathBytes) {
ScopedByteArray path(env, pathBytes);
return (access(&path[0], W_OK) == 0);
}
static jbyteArray java_io_File_getLinkImpl(JNIEnv* env, jobject, jbyteArray pathBytes) {
ScopedByteArray path(env, pathBytes);
// We can't know how big a buffer readlink(2) will need, so we need to
// loop until it says "that fit".
size_t bufSize = 512;
while (true) {
LocalArray<512> buf(bufSize);
ssize_t len = readlink(&path[0], &buf[0], buf.size() - 1);
if (len == -1) {
// An error occurred.
return pathBytes;
}
if (static_cast<size_t>(len) < buf.size() - 1) {
// The buffer was big enough.
// TODO: why do we bother with the NUL termination? (if you change this, remove the "- 1"s above.)
buf[len] = '\0'; // readlink(2) doesn't NUL-terminate.
jbyteArray result = env->NewByteArray(len);
const jbyte* src = reinterpret_cast<const jbyte*>(&buf[0]);
env->SetByteArrayRegion(result, 0, len, src);
return result;
}
// Try again with a bigger buffer.
bufSize *= 2;
}
}
static jboolean java_io_File_setLastModifiedImpl(JNIEnv* env, jobject, jbyteArray pathBytes, jlong ms) {
ScopedByteArray path(env, pathBytes);
// We want to preserve the access time.
struct stat sb;
if (stat(&path[0], &sb) == -1) {
return JNI_FALSE;
}
// TODO: we could get microsecond resolution with utimes(3), "legacy" though it is.
utimbuf times;
times.actime = sb.st_atime;
times.modtime = static_cast<time_t>(ms / 1000);
return (utime(&path[0], &times) == 0);
}
static jboolean java_io_File_setReadOnlyImpl(JNIEnv* env, jobject recv, jbyteArray pathBytes) {
ScopedByteArray path(env, pathBytes);
struct stat sb;
if (stat(&path[0], &sb) == -1) {
return JNI_FALSE;
}
// Strictly, this is set-not-writable (i.e. we leave the execute
// bits untouched), but that's deliberate.
return (chmod(&path[0], sb.st_mode & ~0222) == 0);
}
// Iterates over the filenames in the given directory.
class ScopedReaddir {
public:
ScopedReaddir(const char* path) {
mDirStream = opendir(path);
mIsBad = (mDirStream == NULL);
}
~ScopedReaddir() {
if (mDirStream != NULL) {
closedir(mDirStream);
}
}
// Returns the next filename, or NULL.
const char* next() {
dirent* result = NULL;
int rc = readdir_r(mDirStream, &mEntry, &result);
if (rc != 0) {
mIsBad = true;
return NULL;
}
return (result != NULL) ? result->d_name : NULL;
}
// Has an error occurred on this stream?
bool isBad() const {
return mIsBad;
}
private:
DIR* mDirStream;
dirent mEntry;
bool mIsBad;
};
// DirEntry and DirEntries is a minimal equivalent of std::forward_list
// for the filenames.
struct DirEntry {
DirEntry(const char* filename) : name(strlen(filename)) {
strcpy(&name[0], filename);
next = NULL;
}
// On Linux, the ext family all limit the length of a directory entry to
// less than 256 characters.
LocalArray<256> name;
DirEntry* next;
};
class DirEntries {
public:
DirEntries() : mSize(0), mHead(NULL) {
}
~DirEntries() {
while (mHead) {
pop_front();
}
}
bool push_front(const char* name) {
DirEntry* oldHead = mHead;
mHead = new DirEntry(name);
if (mHead == NULL) {
return false;
}
mHead->next = oldHead;
++mSize;
return true;
}
const char* front() const {
return &mHead->name[0];
}
void pop_front() {
DirEntry* popped = mHead;
if (popped != NULL) {
mHead = popped->next;
--mSize;
delete popped;
}
}
size_t size() const {
return mSize;
}
private:
size_t mSize;
DirEntry* mHead;
};
// Reads the directory referred to by 'pathBytes', adding each directory entry
// to 'entries'.
static bool readDirectory(JNIEnv* env, jbyteArray pathBytes, DirEntries& entries) {
ScopedByteArray path(env, pathBytes);
ScopedReaddir dir(&path[0]);
if (dir.isBad()) {
return false;
}
const char* filename;
while ((filename = dir.next()) != NULL) {
if (strcmp(filename, ".") != 0 && strcmp(filename, "..") != 0) {
if (!entries.push_front(filename)) {
jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
return false;
}
}
}
return true;
}
static jobjectArray java_io_File_listImpl(JNIEnv* env, jobject, jbyteArray pathBytes) {
// Read the directory entries into an intermediate form.
DirEntries files;
if (!readDirectory(env, pathBytes, files)) {
return NULL;
}
// Translate the intermediate form into a Java String[].
jclass stringClass = env->FindClass("java/lang/String");
if (stringClass == NULL) {
return NULL;
}
jobjectArray result = env->NewObjectArray(files.size(), stringClass, NULL);
for (int i = 0; files.size() != 0; files.pop_front(), ++i) {
jstring javaFilename = env->NewStringUTF(files.front());
if (env->ExceptionCheck()) {
return NULL;
}
env->SetObjectArrayElement(result, i, javaFilename);
if (env->ExceptionCheck()) {
return NULL;
}
env->DeleteLocalRef(javaFilename);
}
return result;
}
static jboolean java_io_File_mkdirImpl(JNIEnv* env, jobject, jbyteArray pathBytes) {
ScopedByteArray path(env, pathBytes);
// On Android, we don't want default permissions to allow global access.
return (mkdir(&path[0], S_IRWXU) == 0);
}
static jboolean java_io_File_createNewFileImpl(JNIEnv* env, jobject, jbyteArray pathBytes) {
ScopedByteArray path(env, pathBytes);
// On Android, we don't want default permissions to allow global access.
ScopedFd fd(open(&path[0], O_CREAT | O_EXCL, 0600));
if (fd.get() != -1) {
// We created a new file. Success!
return JNI_TRUE;
}
if (errno == EEXIST) {
// The file already exists.
return JNI_FALSE;
}
jniThrowIOException(env, errno);
return JNI_FALSE; // Ignored by Java; keeps the C++ compiler happy.
}
static jboolean java_io_File_renameToImpl(JNIEnv* env, jobject, jbyteArray oldPathBytes, jbyteArray newPathBytes) {
ScopedByteArray oldPath(env, oldPathBytes);
ScopedByteArray newPath(env, newPathBytes);
return (rename(&oldPath[0], &newPath[0]) == 0);
}
static JNINativeMethod gMethods[] = {
/* name, signature, funcPtr */
{ "createNewFileImpl", "([B)Z", (void*) java_io_File_createNewFileImpl },
{ "deleteImpl", "([B)Z", (void*) java_io_File_deleteImpl },
{ "existsImpl", "([B)Z", (void*) java_io_File_existsImpl },
{ "getCanonImpl", "([B)[B", (void*) java_io_File_getCanonImpl },
{ "getLinkImpl", "([B)[B", (void*) java_io_File_getLinkImpl },
{ "isDirectoryImpl", "([B)Z", (void*) java_io_File_isDirectoryImpl },
{ "isFileImpl", "([B)Z", (void*) java_io_File_isFileImpl },
{ "isReadableImpl", "([B)Z", (void*) java_io_File_isReadableImpl },
{ "isWritableImpl", "([B)Z", (void*) java_io_File_isWritableImpl },
{ "lastModifiedImpl", "([B)J", (void*) java_io_File_lastModifiedImpl },
{ "lengthImpl", "([B)J", (void*) java_io_File_lengthImpl },
{ "listImpl", "([B)[Ljava/lang/String;", (void*) java_io_File_listImpl },
{ "mkdirImpl", "([B)Z", (void*) java_io_File_mkdirImpl },
{ "renameToImpl", "([B[B)Z",(void*) java_io_File_renameToImpl },
{ "setLastModifiedImpl","([BJ)Z", (void*) java_io_File_setLastModifiedImpl },
{ "setReadOnlyImpl", "([B)Z", (void*) java_io_File_setReadOnlyImpl },
};
int register_java_io_File(JNIEnv* env) {
return jniRegisterNativeMethods(env, "java/io/File",
gMethods, NELEM(gMethods));
}