Home | History | Annotate | Download | only in native
      1 /*
      2  *  Licensed to the Apache Software Foundation (ASF) under one or more
      3  *  contributor license agreements.  See the NOTICE file distributed with
      4  *  this work for additional information regarding copyright ownership.
      5  *  The ASF licenses this file to You under the Apache License, Version 2.0
      6  *  (the "License"); you may not use this file except in compliance with
      7  *  the License.  You may obtain a copy of the License at
      8  *
      9  *     http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  *  Unless required by applicable law or agreed to in writing, software
     12  *  distributed under the License is distributed on an "AS IS" BASIS,
     13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  *  See the License for the specific language governing permissions and
     15  *  limitations under the License.
     16  */
     17 
     18 #include "AndroidSystemNatives.h"
     19 #include "JNIHelp.h"
     20 #include "LocalArray.h"
     21 #include "ScopedByteArray.h"
     22 #include "ScopedFd.h"
     23 
     24 #include <string.h>
     25 #include <fcntl.h>
     26 #include <time.h>
     27 #include <utime.h>
     28 #include <unistd.h>
     29 #include <stdlib.h>
     30 #include <sys/types.h>
     31 #include <sys/stat.h>
     32 #include <dirent.h>
     33 #include <errno.h>
     34 
     35 // BEGIN android-note: this file has been extensively rewritten to
     36 // remove fixed-length buffers, buffer overruns, duplication, and
     37 // poor choices of where to divide the work between Java and native
     38 // code.
     39 
     40 static jbyteArray java_io_File_getCanonImpl(JNIEnv* env, jobject, jbyteArray pathBytes) {
     41     ScopedByteArray path(env, pathBytes);
     42     // The only thing this native code currently does is truncate the byte[] at
     43     // the first NUL.
     44     // TODO: this is completely pointless. we should do this in Java, or do all of getCanonicalPath in native code. (realpath(2)?)
     45     size_t length = strlen(&path[0]);
     46     jbyteArray result = env->NewByteArray(length);
     47     env->SetByteArrayRegion(result, 0, length, path.bytes());
     48     return result;
     49 }
     50 
     51 static jboolean java_io_File_deleteImpl(JNIEnv* env, jobject, jbyteArray pathBytes) {
     52     ScopedByteArray path(env, pathBytes);
     53     return (remove(&path[0]) == 0);
     54 }
     55 
     56 static bool doStat(JNIEnv* env, jbyteArray pathBytes, struct stat& sb) {
     57     ScopedByteArray path(env, pathBytes);
     58     return (stat(&path[0], &sb) == 0);
     59 }
     60 
     61 static jlong java_io_File_lengthImpl(JNIEnv* env, jobject, jbyteArray pathBytes) {
     62     struct stat sb;
     63     if (!doStat(env, pathBytes, sb)) {
     64         // We must return 0 for files that don't exist.
     65         // TODO: shouldn't we throw an IOException for ELOOP or EACCES?
     66         return 0;
     67     }
     68 
     69     /*
     70      * This android-changed code explicitly treats non-regular files (e.g.,
     71      * sockets and block-special devices) as having size zero. Some synthetic
     72      * "regular" files may report an arbitrary non-zero size, but
     73      * in these cases they generally report a block count of zero.
     74      * So, use a zero block count to trump any other concept of
     75      * size.
     76      *
     77      * TODO: why do we do this?
     78      */
     79     if (!S_ISREG(sb.st_mode) || sb.st_blocks == 0) {
     80         return 0;
     81     }
     82     return sb.st_size;
     83 }
     84 
     85 static jlong java_io_File_lastModifiedImpl(JNIEnv* env, jobject, jbyteArray pathBytes) {
     86     struct stat sb;
     87     if (!doStat(env, pathBytes, sb)) {
     88         return 0;
     89     }
     90     return static_cast<jlong>(sb.st_mtime) * 1000L;
     91 }
     92 
     93 static jboolean java_io_File_isDirectoryImpl(JNIEnv* env, jobject, jbyteArray pathBytes) {
     94     struct stat sb;
     95     return (doStat(env, pathBytes, sb) && S_ISDIR(sb.st_mode));
     96 }
     97 
     98 static jboolean java_io_File_isFileImpl(JNIEnv* env, jobject, jbyteArray pathBytes) {
     99     struct stat sb;
    100     return (doStat(env, pathBytes, sb) && S_ISREG(sb.st_mode));
    101 }
    102 
    103 static jboolean java_io_File_existsImpl(JNIEnv* env, jobject, jbyteArray pathBytes) {
    104     ScopedByteArray path(env, pathBytes);
    105     return (access(&path[0], F_OK) == 0);
    106 }
    107 
    108 static jboolean java_io_File_isReadableImpl(JNIEnv* env, jobject, jbyteArray pathBytes) {
    109     ScopedByteArray path(env, pathBytes);
    110     return (access(&path[0], R_OK) == 0);
    111 }
    112 
    113 static jboolean java_io_File_isWritableImpl(JNIEnv* env, jobject recv, jbyteArray pathBytes) {
    114     ScopedByteArray path(env, pathBytes);
    115     return (access(&path[0], W_OK) == 0);
    116 }
    117 
    118 static jbyteArray java_io_File_getLinkImpl(JNIEnv* env, jobject, jbyteArray pathBytes) {
    119     ScopedByteArray path(env, pathBytes);
    120 
    121     // We can't know how big a buffer readlink(2) will need, so we need to
    122     // loop until it says "that fit".
    123     size_t bufSize = 512;
    124     while (true) {
    125         LocalArray<512> buf(bufSize);
    126         ssize_t len = readlink(&path[0], &buf[0], buf.size() - 1);
    127         if (len == -1) {
    128             // An error occurred.
    129             return pathBytes;
    130         }
    131         if (len < buf.size() - 1) {
    132             // The buffer was big enough.
    133             // TODO: why do we bother with the NUL termination? (if you change this, remove the "- 1"s above.)
    134             buf[len] = '\0'; // readlink(2) doesn't NUL-terminate.
    135             jbyteArray result = env->NewByteArray(len);
    136             const jbyte* src = reinterpret_cast<const jbyte*>(&buf[0]);
    137             env->SetByteArrayRegion(result, 0, len, src);
    138             return result;
    139         }
    140         // Try again with a bigger buffer.
    141         bufSize *= 2;
    142     }
    143 }
    144 
    145 static jboolean java_io_File_setLastModifiedImpl(JNIEnv* env, jobject, jbyteArray pathBytes, jlong ms) {
    146     ScopedByteArray path(env, pathBytes);
    147 
    148     // We want to preserve the access time.
    149     struct stat sb;
    150     if (stat(&path[0], &sb) == -1) {
    151         return JNI_FALSE;
    152     }
    153 
    154     // TODO: we could get microsecond resolution with utimes(3), "legacy" though it is.
    155     utimbuf times;
    156     times.actime = sb.st_atime;
    157     times.modtime = static_cast<time_t>(ms / 1000);
    158     return (utime(&path[0], &times) == 0);
    159 }
    160 
    161 static jboolean java_io_File_setReadOnlyImpl(JNIEnv* env, jobject recv, jbyteArray pathBytes) {
    162     ScopedByteArray path(env, pathBytes);
    163 
    164     struct stat sb;
    165     if (stat(&path[0], &sb) == -1) {
    166         return JNI_FALSE;
    167     }
    168 
    169     // Strictly, this is set-not-writable (i.e. we leave the execute
    170     // bits untouched), but that's deliberate.
    171     return (chmod(&path[0], sb.st_mode & ~0222) == 0);
    172 }
    173 
    174 // Iterates over the filenames in the given directory.
    175 class ScopedReaddir {
    176 public:
    177     ScopedReaddir(const char* path) {
    178         mDirStream = opendir(path);
    179         mIsBad = (mDirStream == NULL);
    180     }
    181 
    182     ~ScopedReaddir() {
    183         if (mDirStream != NULL) {
    184             closedir(mDirStream);
    185         }
    186     }
    187 
    188     // Returns the next filename, or NULL.
    189     const char* next() {
    190         dirent* result = NULL;
    191         int rc = readdir_r(mDirStream, &mEntry, &result);
    192         if (rc != 0) {
    193             mIsBad = true;
    194             return NULL;
    195         }
    196         return (result != NULL) ? result->d_name : NULL;
    197     }
    198 
    199     // Has an error occurred on this stream?
    200     bool isBad() const {
    201         return mIsBad;
    202     }
    203 
    204 private:
    205     DIR* mDirStream;
    206     dirent mEntry;
    207     bool mIsBad;
    208 };
    209 
    210 // DirEntry and DirEntries is a minimal equivalent of std::forward_list
    211 // for the filenames.
    212 struct DirEntry {
    213     DirEntry(const char* filename) : name(strlen(filename)) {
    214         strcpy(&name[0], filename);
    215         next = NULL;
    216     }
    217     // On Linux, the ext family all limit the length of a directory entry to
    218     // less than 256 characters.
    219     LocalArray<256> name;
    220     DirEntry* next;
    221 };
    222 
    223 class DirEntries {
    224 public:
    225     DirEntries() : mSize(0), mHead(NULL) {
    226     }
    227 
    228     ~DirEntries() {
    229         while (mHead) {
    230             pop_front();
    231         }
    232     }
    233 
    234     bool push_front(const char* name) {
    235         DirEntry* oldHead = mHead;
    236         mHead = new DirEntry(name);
    237         if (mHead == NULL) {
    238             return false;
    239         }
    240         mHead->next = oldHead;
    241         ++mSize;
    242         return true;
    243     }
    244 
    245     const char* front() const {
    246         return &mHead->name[0];
    247     }
    248 
    249     void pop_front() {
    250         DirEntry* popped = mHead;
    251         if (popped != NULL) {
    252             mHead = popped->next;
    253             --mSize;
    254             delete popped;
    255         }
    256     }
    257 
    258     size_t size() const {
    259         return mSize;
    260     }
    261 
    262 private:
    263     size_t mSize;
    264     DirEntry* mHead;
    265 };
    266 
    267 // Reads the directory referred to by 'pathBytes', adding each directory entry
    268 // to 'entries'.
    269 static bool readDirectory(JNIEnv* env, jbyteArray pathBytes, DirEntries& entries) {
    270     ScopedByteArray path(env, pathBytes);
    271     ScopedReaddir dir(&path[0]);
    272     if (dir.isBad()) {
    273         return false;
    274     }
    275     const char* filename;
    276     while ((filename = dir.next()) != NULL) {
    277         if (strcmp(filename, ".") != 0 && strcmp(filename, "..") != 0) {
    278             if (!entries.push_front(filename)) {
    279                 jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
    280                 return false;
    281             }
    282         }
    283     }
    284     return true;
    285 }
    286 
    287 static jobjectArray java_io_File_listImpl(JNIEnv* env, jobject, jbyteArray pathBytes) {
    288     // Read the directory entries into an intermediate form.
    289     DirEntries files;
    290     if (!readDirectory(env, pathBytes, files)) {
    291         return NULL;
    292     }
    293     // Translate the intermediate form into a Java String[].
    294     jclass stringClass = env->FindClass("java/lang/String");
    295     if (stringClass == NULL) {
    296         return NULL;
    297     }
    298     jobjectArray result = env->NewObjectArray(files.size(), stringClass, NULL);
    299     for (int i = 0; files.size() != 0; files.pop_front(), ++i) {
    300         jstring javaFilename = env->NewStringUTF(files.front());
    301         if (env->ExceptionCheck()) {
    302             return NULL;
    303         }
    304         env->SetObjectArrayElement(result, i, javaFilename);
    305         if (env->ExceptionCheck()) {
    306             return NULL;
    307         }
    308         env->DeleteLocalRef(javaFilename);
    309     }
    310     return result;
    311 }
    312 
    313 static jboolean java_io_File_mkdirImpl(JNIEnv* env, jobject, jbyteArray pathBytes) {
    314     ScopedByteArray path(env, pathBytes);
    315     // On Android, we don't want default permissions to allow global access.
    316     return (mkdir(&path[0], S_IRWXU) == 0);
    317 }
    318 
    319 static jboolean java_io_File_createNewFileImpl(JNIEnv* env, jobject, jbyteArray pathBytes) {
    320     ScopedByteArray path(env, pathBytes);
    321     // On Android, we don't want default permissions to allow global access.
    322     ScopedFd fd(open(&path[0], O_CREAT | O_EXCL, 0600));
    323     if (fd.get() != -1) {
    324         // We created a new file. Success!
    325         return JNI_TRUE;
    326     }
    327     if (errno == EEXIST) {
    328         // The file already exists.
    329         return JNI_FALSE;
    330     }
    331     jniThrowIOException(env, errno);
    332     return JNI_FALSE; // Ignored by Java; keeps the C++ compiler happy.
    333 }
    334 
    335 static jboolean java_io_File_renameToImpl(JNIEnv* env, jobject, jbyteArray oldPathBytes, jbyteArray newPathBytes) {
    336     ScopedByteArray oldPath(env, oldPathBytes);
    337     ScopedByteArray newPath(env, newPathBytes);
    338     return (rename(&oldPath[0], &newPath[0]) == 0);
    339 }
    340 
    341 static JNINativeMethod gMethods[] = {
    342     /* name, signature, funcPtr */
    343     { "createNewFileImpl",  "([B)Z",  (void*) java_io_File_createNewFileImpl },
    344     { "deleteImpl",         "([B)Z",  (void*) java_io_File_deleteImpl },
    345     { "existsImpl",         "([B)Z",  (void*) java_io_File_existsImpl },
    346     { "getCanonImpl",       "([B)[B", (void*) java_io_File_getCanonImpl },
    347     { "getLinkImpl",        "([B)[B", (void*) java_io_File_getLinkImpl },
    348     { "isDirectoryImpl",    "([B)Z",  (void*) java_io_File_isDirectoryImpl },
    349     { "isFileImpl",         "([B)Z",  (void*) java_io_File_isFileImpl },
    350     { "isReadableImpl",     "([B)Z",  (void*) java_io_File_isReadableImpl },
    351     { "isWritableImpl",     "([B)Z",  (void*) java_io_File_isWritableImpl },
    352     { "lastModifiedImpl",   "([B)J",  (void*) java_io_File_lastModifiedImpl },
    353     { "lengthImpl",         "([B)J",  (void*) java_io_File_lengthImpl },
    354     { "listImpl",           "([B)[Ljava/lang/String;", (void*) java_io_File_listImpl },
    355     { "mkdirImpl",          "([B)Z",  (void*) java_io_File_mkdirImpl },
    356     { "renameToImpl",       "([B[B)Z",(void*) java_io_File_renameToImpl },
    357     { "setLastModifiedImpl","([BJ)Z", (void*) java_io_File_setLastModifiedImpl },
    358     { "setReadOnlyImpl",    "([B)Z",  (void*) java_io_File_setReadOnlyImpl },
    359 };
    360 int register_java_io_File(JNIEnv* env) {
    361     return jniRegisterNativeMethods(env, "java/io/File",
    362             gMethods, NELEM(gMethods));
    363 }
    364